@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,27 @@
1
+ -- Fresh installation only. Queue and domain state share one transaction.
2
+ CREATE TABLE enterprise_queries.schema_version(
3
+ version integer PRIMARY KEY CHECK(version=1),source_sha256 text NOT NULL,queue_version text NOT NULL
4
+ );
5
+ CREATE TABLE enterprise_queries.requests(
6
+ id uuid PRIMARY KEY,account_id text NOT NULL,principal_key text NOT NULL,
7
+ session_sha256 text NOT NULL,request_key uuid NOT NULL,
8
+ question_mac text NOT NULL,question_ciphertext text,
9
+ state text NOT NULL CHECK(state IN('queued','running','ready','failed','cancelled','expired')),
10
+ attempt integer NOT NULL DEFAULT 0 CHECK(attempt BETWEEN 0 AND 3),
11
+ lease_token uuid,lease_until timestamptz,result_ciphertext text,
12
+ error_code text CHECK(error_code IN('source','setup','signin','unavailable','answer')),
13
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
14
+ updated_at timestamptz NOT NULL DEFAULT clock_timestamp(),expires_at timestamptz NOT NULL,
15
+ UNIQUE(account_id,principal_key,session_sha256,request_key),
16
+ FOREIGN KEY(account_id,principal_key)REFERENCES enterprise_auth.memberships,
17
+ CHECK((state='running')=(lease_token IS NOT NULL AND lease_until IS NOT NULL)),
18
+ CHECK((state='ready')=(result_ciphertext IS NOT NULL))
19
+ );
20
+ CREATE INDEX enterprise_queries_requests_recovery ON enterprise_queries.requests(state,lease_until,expires_at);
21
+ CREATE INDEX enterprise_queries_jobs_domain ON enterprise_queries.procrastinate_jobs((args->>'query_id'),task_name,status);
22
+ CREATE TABLE enterprise_queries.events(
23
+ id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
24
+ query_id uuid NOT NULL,account_id text NOT NULL,principal_key text NOT NULL,
25
+ event text NOT NULL CHECK(event IN('submitted','claimed','ready','released','retry_pending','failed','cancelled','expired')),
26
+ attempt integer NOT NULL,created_at timestamptz NOT NULL DEFAULT clock_timestamp()
27
+ );
@@ -0,0 +1,111 @@
1
+ """Native credential/engine composition for queued questions and delayed release."""
2
+ import asyncio
3
+ import threading
4
+ import logging
5
+ import procrastinate
6
+ import psycopg
7
+ from enterprise_queries import QueryLeaseLost
8
+ from enterprise_sources import SourceDenied,SourceUnavailable,SourceChanged
9
+ from public_faq import InvalidAnswer
10
+ from enterprise_knowledge import current_engine
11
+ from enterprise_audit import KnowledgeAudit
12
+
13
+
14
+ class EnterpriseAnswers:
15
+ def __init__(self,store,connections,make_engine,*,extraction_cache=None,audit=None):
16
+ if not isinstance(connections,dict)or not 1<=len(connections)<=20 or not callable(make_engine):raise ValueError('Supply owned native question adapters')
17
+ if any(entry.driver.store is not store.auth for entry in connections.values()):raise ValueError('Question/source stores must use the same owned authority')
18
+ if extraction_cache is not None and(extraction_cache.auth is not store.auth or extraction_cache.account_id!=store.account_id):raise ValueError('Extraction cache must use the same owned company and authority')
19
+ if audit is not None and(not isinstance(audit,KnowledgeAudit)or audit.auth is not store.auth or audit.account_id!=store.account_id):raise ValueError('Question audit must use the same owned company and authority')
20
+ self.store=store;self.connections=connections;self.make_engine=make_engine;self.extraction_cache=extraction_cache;self.audit=audit
21
+
22
+ def engine(self,principal,authorize,*,query_id=None,attempt=None):
23
+ engine=current_engine(self.store.auth,self.connections,principal,self.make_engine,authorize)
24
+ if self.audit is not None:engine.audit=self.audit.bind(principal,query_id=query_id,attempt=attempt)
25
+ if self.extraction_cache is not None:
26
+ engine.decode=self.extraction_cache.wrap(engine.decode,lambda:engine.authorize(principal.key))
27
+ return engine
28
+
29
+ def execute(self,identity):
30
+ lease=self.store.claim(identity)
31
+ if lease is None:return
32
+ stop=threading.Event();lost=threading.Event()
33
+ def heartbeat():
34
+ while not stop.wait(max(1,self.store.lease_seconds//3)):
35
+ try:self.store.guard(lease,renew=True)
36
+ except Exception:lost.set();return
37
+ def authorize(subject):
38
+ if lost.is_set():raise QueryLeaseLost('Question worker lost current authority')
39
+ if subject!=lease.principal.key:raise SourceDenied('company_identity_required')
40
+ self.store.guard(lease)
41
+ thread=threading.Thread(target=heartbeat,daemon=True);thread.start()
42
+ try:
43
+ authorize(lease.principal.key)
44
+ engine=self.engine(lease.principal,authorize,query_id=lease.id,attempt=lease.attempt)
45
+ receipt=engine.answer_receipt(lease.question,lease.principal.key)
46
+ authorize(lease.principal.key)
47
+ self.store.complete(lease,receipt)
48
+ except QueryLeaseLost:return
49
+ except SourceDenied as error:
50
+ code='setup'if str(error)=='source_mapping_required'else'source'if str(error)in {'source_reconnection_required','source_consent_required'}else'signin'if str(error).startswith('company_')else'unavailable'
51
+ self.store.failed(lease,code,retry=code=='unavailable')
52
+ except InvalidAnswer:self.store.failed(lease,'answer')
53
+ except SourceUnavailable:self.store.failed(lease,'unavailable',retry=True)
54
+ except Exception:self.store.failed(lease,'unavailable')
55
+ finally:stop.set();thread.join(timeout=2)
56
+
57
+ def status(self,session,identity):
58
+ row=self.store.get(session,identity)
59
+ result={'id':identity,'status':row['state']}
60
+ if row['state']=='failed':result['errorCode']=row['error_code'];return result
61
+ if row['state']!='ready':return result
62
+ receipt=self.store.open_result(row)
63
+ def authorize(subject):
64
+ if subject!=session.principal.key:raise SourceDenied('company_identity_required')
65
+ current=self.store.get(session,identity)
66
+ if current['state']!='ready'or current['attempt']!=row['attempt']:raise QueryLeaseLost('Question changed before release')
67
+ try:
68
+ engine=self.engine(session.principal,authorize,query_id=identity,attempt=row['attempt'])
69
+ answer=engine.release(receipt,session.principal.key)
70
+ self.store.released(session,identity,row['attempt'])
71
+ return {**result,'result':answer}
72
+ except SourceChanged:
73
+ self.store.requeue(session,identity,row['attempt'])
74
+ return {'id':identity,'status':'queued'}
75
+ except QueryLeaseLost:
76
+ return {'id':identity,'status':self.store.get(session,identity)['state']}
77
+
78
+
79
+ class EnterpriseQueryWorker:
80
+ def __init__(self,dsn,service,*,document_recovery=None):
81
+ if document_recovery is not None and not callable(document_recovery):raise ValueError('Use the owned document reconciler')
82
+ self.service=service
83
+ self.document_recovery=document_recovery
84
+ async def refresh():return await asyncio.to_thread(dsn)
85
+ self.app=procrastinate.App(connector=procrastinate.PsycopgConnector(conninfo=refresh if callable(dsn)else dsn,min_size=1,max_size=4,max_lifetime=600,kwargs={'connect_timeout':5,'options':'-c search_path=pg_catalog,enterprise_queries,pg_temp -c statement_timeout=10000 -c lock_timeout=3000'}))
86
+ async def execute(query_id):await asyncio.to_thread(service.execute,query_id)
87
+ self.task=self.app.task(name=service.store.task.name,queue=service.store.task.queue)(execute)
88
+
89
+ async def run(self,*,concurrency=2,**options):
90
+ if type(concurrency)is not int or not 1<=concurrency<=4:raise ValueError('Use bounded owned query concurrency')
91
+ async def recover():
92
+ while True:
93
+ try:
94
+ await asyncio.to_thread(self.service.store.recover)
95
+ if self.service.extraction_cache is not None:await asyncio.to_thread(self.service.extraction_cache.purge)
96
+ except(psycopg.OperationalError,SourceUnavailable):logging.getLogger(__name__).warning('Question recovery is temporarily unavailable')
97
+ await asyncio.sleep(15)
98
+ async def documents():
99
+ # Separate loop: slow native deletion never blocks query lease or
100
+ # cache recovery. SQL cleanup claims prevent overlapping ownership.
101
+ while True:
102
+ try:await asyncio.to_thread(self.document_recovery,limit=1)
103
+ except(psycopg.Error,SourceUnavailable):logging.getLogger(__name__).warning('Document cleanup is temporarily unavailable')
104
+ await asyncio.sleep(15)
105
+ async with self.app.open_async():
106
+ async with asyncio.TaskGroup()as group:
107
+ recovery=group.create_task(recover())
108
+ cleanup=group.create_task(documents())if self.document_recovery is not None else None
109
+ await self.app.run_worker_async(queues=[self.task.queue],concurrency=concurrency,**options)
110
+ recovery.cancel()
111
+ if cleanup is not None:cleanup.cancel()
@@ -0,0 +1,110 @@
1
+ """Startup secret injections for the owned read-only private assistant.
2
+
3
+ Native platforms resolve pinned secret references. This adapter never downloads
4
+ cloud secrets, generates a replacement encryption key or writes into an export.
5
+ All replicas must use the same reviewed encryption-key version; coordinated key
6
+ migration/backup is separate operator work, not automatic hot rotation.
7
+ """
8
+ import atexit
9
+ import os
10
+ from pathlib import Path
11
+ import shutil
12
+ import tempfile
13
+ from cryptography.fernet import Fernet
14
+ from enterprise_sources import SourceUnavailable
15
+
16
+ FIELDS = {
17
+ 'encryption': ('ENTERPRISE_ENCRYPTION_KEY', 'ENTERPRISE_ENCRYPTION_KEY_FILE', 44),
18
+ 'graph': ('SHAREPOINT_CLIENT_SECRET', 'SHAREPOINT_CLIENT_SECRET_FILE', 16000),
19
+ 'confluence': ('CONFLUENCE_CLIENT_SECRET', 'CONFLUENCE_CLIENT_SECRET_FILE', 16000),
20
+ 'database_password': ('DATABASE_PASSWORD', 'DATABASE_PASSWORD_FILE', 500),
21
+ 'database_ca': ('DATABASE_CA_PEM', 'DATABASE_CA_FILE', 256000),
22
+ }
23
+
24
+
25
+ def decode(name, raw):
26
+ if not raw or len(raw) > FIELDS[name][2] or b'\0' in raw:
27
+ raise ValueError('Invalid owned secret value')
28
+ value = raw.decode('utf-8')
29
+ if name == 'encryption':
30
+ if len(raw) != 44:
31
+ raise ValueError('Use the existing exact Fernet key')
32
+ Fernet(raw)
33
+ if name == 'database_ca' and '-----BEGIN CERTIFICATE-----' not in value:
34
+ raise ValueError('Use the native database CA certificate')
35
+ return value
36
+
37
+
38
+ class SecretFiles:
39
+ def __init__(self, provider, environment, role):
40
+ if provider not in {'aws', 'azure', 'gcp', 'ibm'} or role not in {'api', 'worker', 'operator'}:
41
+ raise ValueError('Supply the owned private workload provider and role')
42
+ # Read-only assistants have no action credentials. Native workload
43
+ # identities must not silently fall back to inherited developer keys.
44
+ forbidden = ['DESTINATION_TOKEN', 'DESTINATION_TOKEN_FILE', 'PARSER_SHARED_TOKEN', 'PARSER_SHARED_TOKEN_FILE',
45
+ 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN', 'AZURE_CLIENT_SECRET',
46
+ 'GOOGLE_APPLICATION_CREDENTIALS', 'IBMCLOUD_API_KEY', 'OPENAI_API_KEY']
47
+ if provider != 'ibm': forbidden += ['DATABASE_PASSWORD', 'DATABASE_PASSWORD_FILE']
48
+ if role == 'operator': forbidden += [value for name in ['encryption', 'graph', 'confluence'] for value in FIELDS[name][:2]]
49
+ if any(key in environment for key in forbidden):
50
+ raise ValueError('Unexpected static cloud, parser or action authority in the private workload')
51
+ self.env, self.directory, self.closed = dict(environment), None, False
52
+ required = ['encryption', 'graph', 'confluence'] if role != 'operator' else []
53
+ if provider != 'gcp': required.append('database_ca')
54
+ if provider == 'ibm': required.append('database_password')
55
+ selected = []
56
+ for name, (direct, pointer, maximum) in FIELDS.items():
57
+ if direct in environment and pointer in environment:
58
+ raise ValueError('Conflicting native secret injection and mounted file')
59
+ if direct in environment:
60
+ value = environment[direct]
61
+ if not isinstance(value, str): raise ValueError('Invalid native secret injection')
62
+ decode(name, value.encode('utf-8'))
63
+ selected.append((name, direct, pointer, value))
64
+ elif pointer in environment:
65
+ path = environment[pointer]
66
+ if not isinstance(path, str) or not Path(path).is_absolute():
67
+ raise ValueError('Use the owned mounted secret path')
68
+ elif name in required:
69
+ raise ValueError('Missing required native secret reference')
70
+ try:
71
+ if selected:
72
+ self.directory = Path(tempfile.mkdtemp(prefix='atmem-enterprise-secrets-'))
73
+ self.directory.chmod(0o700)
74
+ for name, direct, pointer, value in selected:
75
+ path = self.directory / name
76
+ descriptor = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
77
+ with os.fdopen(descriptor, 'wb') as stream: stream.write(value.encode('utf-8'))
78
+ self.env[pointer] = str(path); self.env.pop(direct, None)
79
+ # Fail startup for unreadable/invalid required files; do not generate
80
+ # new keys, invent credentials or defer broken setup to a basic user.
81
+ for name in required: self.read(name)
82
+ if environment is os.environ:
83
+ for _, direct, _, _ in selected: os.environ.pop(direct, None)
84
+ atexit.register(self.close)
85
+ except BaseException:
86
+ self.close(); raise
87
+
88
+ def __repr__(self): return '<Owned private-assistant secret files>'
89
+
90
+ def read(self, name):
91
+ if name not in FIELDS: raise ValueError('Unknown owned secret purpose')
92
+ try:
93
+ if self.closed: raise ValueError()
94
+ _, pointer, maximum = FIELDS[name]
95
+ path = self.env[pointer]
96
+ with Path(path).open('rb') as stream: raw = stream.read(maximum + 1)
97
+ return decode(name, raw)
98
+ except Exception:
99
+ raise SourceUnavailable('enterprise_secret_unavailable') from None
100
+
101
+ def reader(self, name):
102
+ if name not in FIELDS: raise ValueError('Unknown owned secret purpose')
103
+ return lambda: self.read(name)
104
+
105
+ def close(self):
106
+ if self.directory is not None: shutil.rmtree(self.directory, ignore_errors=True)
107
+ self.closed = True
108
+
109
+ def __enter__(self): return self
110
+ def __exit__(self, *args): self.close()
@@ -0,0 +1,95 @@
1
+ """Opaque, durable company sessions; raw cookies/JWTs never persist in SQL."""
2
+ from dataclasses import dataclass,field
3
+ import hmac
4
+ import secrets
5
+ import time
6
+ import re
7
+ from enterprise_auth_store import digest,secret_binding
8
+ from enterprise_identity import CompanyPrincipal
9
+ from enterprise_sources import SourceDenied
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class BrowserSession:
14
+ principal: CompanyPrincipal
15
+ id: str = field(repr=False)
16
+ browser_nonce: str = field(repr=False)
17
+ csrf_sha256: str = field(repr=False)
18
+ csrf: str = field(repr=False)
19
+
20
+ def require_csrf(self,token):
21
+ secret_binding(token)
22
+ if not hmac.compare_digest(digest(token),self.csrf_sha256):raise SourceDenied('browser_request_denied')
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class CreatedSession:
27
+ token: str = field(repr=False)
28
+ csrf: str = field(repr=False)
29
+ max_age: int
30
+
31
+
32
+ class CompanySessions:
33
+ def __init__(self,auth,*,domain=None):
34
+ if domain is not None and(not isinstance(domain,str)or not re.fullmatch('[0-9a-f]{64}',domain)):raise ValueError('Use the owned company session domain')
35
+ if getattr(auth,'company_domain',None)!=domain:raise ValueError('Session and authority company registrations must match')
36
+ self.auth=auth;self.domain=domain
37
+ def context(self,account,key,session):
38
+ result=['browser-session',account,key,session]
39
+ if self.domain is not None:result+=['company-registration',self.domain]
40
+ return result
41
+
42
+ def private(self,row,identity):
43
+ try:
44
+ private=self.auth.box.open(self.context(row['account_id'],row['principal_key'],identity),row['nonce_ciphertext'])
45
+ if set(private)!={'browser_nonce','csrf'}:raise ValueError()
46
+ secret_binding(private['browser_nonce']);secret_binding(private['csrf'])
47
+ if not hmac.compare_digest(digest(private['csrf']),row['csrf_sha256']):raise ValueError()
48
+ return private
49
+ except Exception:raise SourceDenied('company_identity_required')from None
50
+
51
+ def create(self,principal,previous=None):
52
+ if previous:
53
+ try:secret_binding(previous)
54
+ except SourceDenied:previous=None
55
+ token=secrets.token_urlsafe(32);csrf=secrets.token_urlsafe(32);nonce=secrets.token_urlsafe(32);identity=digest(token)
56
+ with self.auth.transaction()as cur:
57
+ self.auth._authorize(cur,principal)
58
+ previous_owner=cur.execute('SELECT account_id,principal_key FROM enterprise_auth.sessions WHERE session_sha256=%s',(digest(previous),)).fetchone()if previous else None
59
+ owners={principal.key}
60
+ if previous_owner and previous_owner['account_id']==principal.account_id:owners.add(previous_owner['principal_key'])
61
+ # A profile switch touches the prior person's browser row and the new
62
+ # person's quota. Lock both owner domains in one deterministic order.
63
+ for owner in sorted(owners):cur.execute('SELECT pg_advisory_xact_lock(hashtextextended(%s,0))',('enterprise-session:'+principal.account_id+':'+owner,))
64
+ if previous_owner and previous_owner['account_id']==principal.account_id:
65
+ row=cur.execute('SELECT account_id,principal_key,nonce_ciphertext,csrf_sha256,revoked_at,expires_at>clock_timestamp()AS current FROM enterprise_auth.sessions WHERE session_sha256=%s FOR UPDATE',(digest(previous),)).fetchone()
66
+ if row and row['account_id']==principal.account_id:
67
+ if row['principal_key']==principal.key and row['current']and row['revoked_at']is None:
68
+ try:nonce=self.private(row,digest(previous))['browser_nonce']
69
+ except SourceDenied:pass # New verified login gets a fresh nonce; never inherit an invalid/old-domain one.
70
+ cur.execute('UPDATE enterprise_auth.sessions SET revoked_at=clock_timestamp()WHERE session_sha256=%s',(digest(previous),))
71
+ cur.execute("DELETE FROM enterprise_auth.sessions WHERE account_id=%s AND principal_key=%s AND expires_at<clock_timestamp()-interval '1 day'",(principal.account_id,principal.key))
72
+ cur.execute('UPDATE enterprise_auth.sessions SET revoked_at=clock_timestamp()WHERE session_sha256 IN(SELECT session_sha256 FROM enterprise_auth.sessions WHERE account_id=%s AND principal_key=%s AND revoked_at IS NULL ORDER BY created_at DESC OFFSET 9)',(principal.account_id,principal.key))
73
+ encrypted=self.auth.box.seal(self.context(principal.account_id,principal.key,identity),{'browser_nonce':nonce,'csrf':csrf})
74
+ cur.execute('INSERT INTO enterprise_auth.sessions(session_sha256,account_id,principal_key,csrf_sha256,nonce_ciphertext,expires_at)VALUES(%s,%s,%s,%s,%s,to_timestamp(%s))',(identity,principal.account_id,principal.key,digest(csrf),encrypted,principal.expires_at))
75
+ self.auth.event(cur,principal,'company-session','session_started')
76
+ return CreatedSession(token,csrf,max(1,min(3600,int(principal.expires_at-time.time()))))
77
+
78
+ def authenticate(self,token):
79
+ try:secret_binding(token)
80
+ except SourceDenied:raise SourceDenied('company_identity_required')from None
81
+ identity=digest(token)
82
+ with self.auth.transaction()as cur:
83
+ row=cur.execute('SELECT s.account_id,s.principal_key,s.csrf_sha256,s.nonce_ciphertext,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()',(identity,)).fetchone()
84
+ if not row:raise SourceDenied('company_identity_required')
85
+ principal=CompanyPrincipal(row['account_id'],row['issuer'],row['subject'],row['expires_at'])
86
+ if principal.key!=row['principal_key']:raise SourceDenied('company_identity_required')
87
+ self.auth._authorize(cur,principal)
88
+ private=self.private(row,identity);nonce=private['browser_nonce'];csrf=private['csrf']
89
+ return BrowserSession(principal,identity,nonce,row['csrf_sha256'],csrf)
90
+
91
+ def revoke(self,session):
92
+ with self.auth.transaction()as cur:
93
+ self.auth._authorize(cur,session.principal)
94
+ cur.execute('UPDATE enterprise_auth.sessions SET revoked_at=clock_timestamp()WHERE session_sha256=%s AND account_id=%s AND principal_key=%s',(session.id,session.principal.account_id,session.principal.key))
95
+ self.auth.event(cur,session.principal,'company-session','session_ended')
@@ -0,0 +1,260 @@
1
+ """Native read-only SharePoint/Confluence source boundary.
2
+
3
+ Per-user delegated credentials come from a trusted OAuth broker, never chat/model
4
+ input or a shared crawler identity. No private documents or permissions are cached.
5
+ These API adapters do not implement that broker, an index, or the model API.
6
+ """
7
+ from dataclasses import dataclass, field
8
+ from html.parser import HTMLParser
9
+ import json
10
+ import re
11
+ import time
12
+ from urllib.error import HTTPError, URLError
13
+ from urllib.parse import quote, urlencode, urlsplit, unquote
14
+ from urllib.request import Request, build_opener, HTTPRedirectHandler, ProxyHandler
15
+
16
+
17
+ class SourceUnavailable(RuntimeError):
18
+ pass
19
+
20
+
21
+ class SourceDenied(SourceUnavailable):
22
+ pass
23
+
24
+
25
+ class SourceChanged(SourceUnavailable):
26
+ pass
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class DelegatedAccess:
31
+ subject: str
32
+ source_subject: str
33
+ token: str = field(repr=False)
34
+ expires_at: float
35
+ kind: str = 'delegated'
36
+
37
+ def require(self, subject, now=None):
38
+ now=time.time()if now is None else now
39
+ if self.kind!='delegated'or not subject or self.subject!=subject or not self.source_subject or not isinstance(self.token,str)or not self.token or len(self.token)>32768 or any(c in self.token for c in '\r\n')or not isinstance(self.expires_at,(int,float))or isinstance(self.expires_at,bool)or not now+5<self.expires_at<now+86401:
40
+ raise SourceDenied('source_identity_unavailable')
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class SourceDocument:
45
+ source: str
46
+ id: str
47
+ version: str
48
+ title: str = field(repr=False)
49
+ text: str = field(repr=False)
50
+ url: str = field(repr=False)
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class Reply:
55
+ status: int
56
+ headers: dict
57
+ body: bytes = field(repr=False)
58
+
59
+
60
+ def checked_url(url,hosts):
61
+ try:parts=urlsplit(url);port=parts.port
62
+ except (TypeError,ValueError):raise SourceUnavailable('invalid_source_url')from None
63
+ if parts.scheme!='https'or parts.hostname not in hosts or port not in {None,443}or parts.username or parts.password or parts.fragment or len(url)>16000 or any(c in url for c in '\r\n'):
64
+ raise SourceUnavailable('invalid_source_url')
65
+ return url
66
+
67
+
68
+ class NoRedirect(HTTPRedirectHandler):
69
+ def redirect_request(self,request,fp,code,msg,headers,newurl):
70
+ return None
71
+
72
+
73
+ class NativeHttp:
74
+ def __init__(self):
75
+ # A source bearer token must not be routed through environment proxies.
76
+ self.opener=build_opener(ProxyHandler({}),NoRedirect())
77
+
78
+ def get(self,url,token,*,hosts,limit=2000000):
79
+ checked_url(url,hosts)
80
+ headers={'Accept':'application/json','Cache-Control':'no-store'}
81
+ if token:headers['Authorization']='Bearer '+token
82
+ try:
83
+ response=self.opener.open(Request(url,headers=headers),timeout=10)
84
+ except HTTPError as error:
85
+ try:
86
+ if error.code in {301,302,303,307,308}:
87
+ return Reply(error.code,dict(error.headers),b'')
88
+ if error.code in {401,403,404,410}:raise SourceDenied('source_access_denied')from None
89
+ raise SourceUnavailable('source_request_failed')from None
90
+ finally:error.close()
91
+ except (URLError,OSError,ValueError):raise SourceUnavailable('source_request_failed')from None
92
+ with response:
93
+ body=response.read(limit+1)
94
+ if len(body)>limit:raise SourceUnavailable('source_response_too_large')
95
+ return Reply(response.status,dict(response.headers),body)
96
+
97
+
98
+ def decoded(reply):
99
+ if reply.status!=200:raise SourceUnavailable('source_response_failed')
100
+ try:value=json.loads(reply.body)
101
+ except (ValueError,UnicodeError):raise SourceUnavailable('invalid_source_response')from None
102
+ if not isinstance(value,dict):raise SourceUnavailable('invalid_source_response')
103
+ return value
104
+
105
+
106
+ def identifier(value):
107
+ if not isinstance(value,str)or not re.fullmatch(r'[A-Za-z0-9_!.,-]{1,256}',value):raise SourceUnavailable('invalid_source_identifier')
108
+ return quote(value,safe='')
109
+
110
+
111
+ def query_text(value):
112
+ if not isinstance(value,str)or not value.strip()or len(value)>1000 or any(ord(c)<32 for c in value):raise SourceUnavailable('invalid_source_query')
113
+ return value.strip()
114
+
115
+
116
+ class GraphSharePoint:
117
+ def __init__(self,drive_id,sharepoint_host,access,*,http=None):
118
+ self.drive_id=drive_id;self.drive=identifier(drive_id);self.source='sharepoint:'+drive_id
119
+ if not isinstance(sharepoint_host,str)or not re.fullmatch(r'[a-z0-9][a-z0-9-]*\.sharepoint\.com',sharepoint_host):raise SourceUnavailable('invalid_sharepoint_host')
120
+ self.host=sharepoint_host;self.access=access;self.http=http or NativeHttp()
121
+ self.base='https://graph.microsoft.com/v1.0'
122
+
123
+ def _get(self,path,subject):
124
+ self.access.require(subject)
125
+ return decoded(self.http.get(self.base+path,self.access.token,hosts={'graph.microsoft.com'}))
126
+
127
+ def identify(self,subject):
128
+ if self._get('/me?$select=id',subject).get('id')!=self.access.source_subject:raise SourceDenied('source_identity_mismatch')
129
+
130
+ def search(self,question,subject,*,limit=20):
131
+ if type(limit)is not int or not 1<=limit<=100:raise SourceUnavailable('invalid_search_limit')
132
+ self.identify(subject)
133
+ q=quote(query_text(question).replace("'","''"),safe='')
134
+ route=f"/drives/{self.drive}/root/search(q='{q}')"
135
+ url=self.base+route+'?'+urlencode({'$top':min(limit,100),'$select':'id,file,parentReference'})
136
+ result=[];seen=set();pages=set()
137
+ while url and len(result)<limit:
138
+ checked_url(url,{'graph.microsoft.com'})
139
+ if unquote(urlsplit(url).path)!=unquote(urlsplit(self.base+route).path)or url in pages or len(pages)>=20:raise SourceUnavailable('invalid_source_paging')
140
+ pages.add(url);self.access.require(subject)
141
+ value=decoded(self.http.get(url,self.access.token,hosts={'graph.microsoft.com'}))
142
+ if not isinstance(value.get('value'),list):raise SourceUnavailable('invalid_source_response')
143
+ for row in value['value']:
144
+ if not isinstance(row,dict):raise SourceUnavailable('invalid_source_response')
145
+ if row.get('parentReference',{}).get('driveId')!=self.drive_id or not isinstance(row.get('file'),dict):continue
146
+ identity=row.get('id');identifier(identity)
147
+ if identity not in seen:seen.add(identity);result.append(identity)
148
+ if len(result)==limit:break
149
+ url=value.get('@odata.nextLink')
150
+ return tuple(result)
151
+
152
+ def metadata(self,identity,subject):
153
+ value=self._get('/drives/'+self.drive+'/items/'+identifier(identity)+'?'+urlencode({'$select':'id,name,eTag,file,parentReference,webUrl,deleted'}),subject)
154
+ if value.get('id')!=identity or value.get('deleted')or not isinstance(value.get('file'),dict)or value.get('parentReference',{}).get('driveId')!=self.drive_id or not isinstance(value.get('eTag'),str)or not value['eTag']:
155
+ raise SourceDenied('source_document_unavailable')
156
+ checked_url(value.get('webUrl',''),{self.host})
157
+ return value
158
+
159
+ def fetch(self,identity,subject,decode):
160
+ self.identify(subject);before=self.metadata(identity,subject)
161
+ self.access.require(subject)
162
+ reply=self.http.get(self.base+'/drives/'+self.drive+'/items/'+identifier(identity)+'/content',self.access.token,hosts={'graph.microsoft.com'},limit=10000000)
163
+ if reply.status==302:
164
+ # Download capability URL is short lived: never persist/log it and
165
+ # never forward the Graph bearer token to the storage endpoint.
166
+ location=reply.headers.get('Location')or reply.headers.get('location')
167
+ checked_url(location,{self.host})
168
+ reply=self.http.get(location,None,hosts={self.host},limit=10000000)
169
+ if reply.status!=200:raise SourceUnavailable('source_download_failed')
170
+ def current():
171
+ value=self.metadata(identity,subject)
172
+ if value['eTag']!=before['eTag']:raise SourceChanged('source_document_changed')
173
+ return value
174
+ source_decoder=getattr(decode,'from_source',None)
175
+ mime=before['file'].get('mimeType','application/octet-stream')
176
+ text=source_decoder(reply.body,mime,current)if callable(source_decoder)else decode(reply.body,mime)
177
+ if not isinstance(text,str)or not text.strip()or len(text)>200000:raise SourceUnavailable('source_document_unsupported')
178
+ after=current()
179
+ title=after.get('name')
180
+ if not isinstance(title,str)or not title or len(title)>1000:raise SourceUnavailable('invalid_source_response')
181
+ return SourceDocument(self.source,identity,after['eTag'],title,text,after['webUrl'])
182
+
183
+ def revalidate(self,document,subject):
184
+ if document.source!=self.source or self.metadata(document.id,subject)['eTag']!=document.version:raise SourceChanged('source_document_changed')
185
+
186
+
187
+ class PageText(HTMLParser):
188
+ def __init__(self):super().__init__(convert_charrefs=True);self.parts=[];self.hidden=0
189
+ def handle_starttag(self,tag,attrs):
190
+ if tag in {'script','style'}:self.hidden+=1
191
+ elif not self.hidden and tag in {'p','br','div','li','h1','h2','h3','tr'}:self.parts.append('\n')
192
+ def handle_endtag(self,tag):
193
+ if tag in {'script','style'}and self.hidden:self.hidden-=1
194
+ def handle_data(self,data):
195
+ if not self.hidden:self.parts.append(data)
196
+
197
+
198
+ class ConfluenceCloud:
199
+ def __init__(self,cloud_id,site_host,spaces,access,*,http=None):
200
+ if not isinstance(cloud_id,str)or not re.fullmatch(r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}',cloud_id):raise SourceUnavailable('invalid_confluence_cloud')
201
+ if not isinstance(site_host,str)or not re.fullmatch(r'[a-z0-9][a-z0-9-]*\.atlassian\.net',site_host):raise SourceUnavailable('invalid_confluence_host')
202
+ if not isinstance(spaces,dict)or not spaces or len(spaces)>100 or any(not isinstance(k,str)or not re.fullmatch(r'[A-Za-z0-9_~-]{1,128}',k)or not isinstance(v,str)or not re.fullmatch(r'[0-9]{1,30}',v)for k,v in spaces.items()):raise SourceUnavailable('invalid_confluence_spaces')
203
+ self.source='confluence:'+cloud_id;self.cloud_id=cloud_id;self.base='https://api.atlassian.com/ex/confluence/'+cloud_id;self.host=site_host;self.space_ids=set(spaces.values());self.space_keys=set(spaces);self.access=access;self.http=http or NativeHttp()
204
+
205
+ def _get(self,path,subject):
206
+ self.access.require(subject)
207
+ return decoded(self.http.get(self.base+path,self.access.token,hosts={'api.atlassian.com'}))
208
+
209
+ def identify(self,subject):
210
+ self.access.require(subject)
211
+ value=decoded(self.http.get('https://api.atlassian.com/me',self.access.token,hosts={'api.atlassian.com'}))
212
+ if value.get('account_id')!=self.access.source_subject or value.get('account_status')!='active':raise SourceDenied('source_identity_mismatch')
213
+ reply=self.http.get('https://api.atlassian.com/oauth/token/accessible-resources',self.access.token,hosts={'api.atlassian.com'})
214
+ try:resources=json.loads(reply.body)
215
+ except (ValueError,UnicodeError):raise SourceUnavailable('invalid_source_response')from None
216
+ if reply.status!=200 or not isinstance(resources,list):raise SourceUnavailable('invalid_source_response')
217
+ allowed=[r for r in resources if isinstance(r,dict)and r.get('id')==self.cloud_id and r.get('url')=='https://'+self.host]
218
+ if len(allowed)!=1 or not isinstance(allowed[0].get('scopes'),list)or 'read:page:confluence'not in allowed[0]['scopes']or not {'search:confluence','read:content-details:confluence'}.intersection(allowed[0]['scopes']):raise SourceDenied('source_site_unavailable')
219
+
220
+ def search(self,question,subject,*,limit=20):
221
+ if type(limit)is not int or not 1<=limit<=100:raise SourceUnavailable('invalid_search_limit')
222
+ self.identify(subject);term=query_text(question).replace('\\','\\\\').replace('"','\\"')
223
+ # IDs and query are data, never executable caller-supplied CQL.
224
+ cql='type=page AND space in ('+','.join('"'+key+'"'for key in sorted(self.space_keys))+') AND text ~ "'+term+'"'
225
+ route='/wiki/rest/api/search';url=self.base+route+'?'+urlencode({'cql':cql,'limit':min(limit,25)})
226
+ result=[];seen=set();pages=set()
227
+ while url and len(result)<limit:
228
+ checked_url(url,{'api.atlassian.com'})
229
+ if urlsplit(url).path!=urlsplit(self.base+route).path or url in pages or len(pages)>=20:raise SourceUnavailable('invalid_source_paging')
230
+ self.access.require(subject);pages.add(url)
231
+ value=decoded(self.http.get(url,self.access.token,hosts={'api.atlassian.com'}))
232
+ if not isinstance(value.get('results'),list):raise SourceUnavailable('invalid_source_response')
233
+ for row in value['results']:
234
+ content=row.get('content',{})if isinstance(row,dict)else{}
235
+ identity=content.get('id')
236
+ if content.get('type')!='page':continue
237
+ if not isinstance(identity,str)or not re.fullmatch(r'[0-9]{1,30}',identity):raise SourceUnavailable('invalid_source_response')
238
+ if identity not in seen:seen.add(identity);result.append(identity)
239
+ if len(result)==limit:break
240
+ next_url=value.get('_links',{}).get('next');url=None
241
+ if next_url:
242
+ if next_url.startswith('/rest/api/search?'):url=self.base+'/wiki'+next_url
243
+ elif next_url.startswith('/wiki/rest/api/search?'):url=self.base+next_url
244
+ else:url=next_url
245
+ return tuple(result)
246
+
247
+ def fetch(self,identity,subject,decode=None):
248
+ if not isinstance(identity,str)or not re.fullmatch(r'[0-9]{1,30}',identity):raise SourceUnavailable('invalid_source_identifier')
249
+ self.identify(subject)
250
+ value=self._get('/wiki/api/v2/pages/'+identity+'?body-format=storage',subject)
251
+ if value.get('id')!=identity or value.get('status')!='current'or value.get('spaceId')not in self.space_ids:raise SourceDenied('source_document_unavailable')
252
+ version=value.get('version',{}).get('number');title=value.get('title');html=value.get('body',{}).get('storage',{}).get('value')
253
+ if type(version)is not int or version<1 or not isinstance(title,str)or not title or len(title)>1000 or not isinstance(html,str)or len(html)>1000000:raise SourceUnavailable('invalid_source_response')
254
+ parser=PageText();parser.feed(html);text=''.join(parser.parts).strip()
255
+ if not text or len(text)>200000:raise SourceUnavailable('source_document_unsupported')
256
+ # Construct a fixed same-site citation, never an API-supplied redirect URL.
257
+ return SourceDocument(self.source,identity,str(version),title,text,'https://'+self.host+'/wiki/pages/viewpage.action?pageId='+identity)
258
+
259
+ def revalidate(self,document,subject):
260
+ if document.source!=self.source or self.fetch(document.id,subject).version!=document.version:raise SourceChanged('source_document_changed')
@@ -0,0 +1,39 @@
1
+ """Owned static browser assets and public OIDC registration configuration."""
2
+ import hashlib
3
+ import json
4
+ from pathlib import Path
5
+ from urllib.parse import urlsplit
6
+ from fastapi.responses import FileResponse
7
+ from enterprise_identity import trusted_https
8
+
9
+
10
+ def company_configuration(identity,config):
11
+ if not isinstance(config,dict)or set(config)!={'clientId','authorizationEndpoint','tokenEndpoint','scopes'}:raise ValueError('Supply only public company OIDC registration fields')
12
+ if not isinstance(config['clientId'],str)or not 1<=len(config['clientId'])<=200:raise ValueError('Use the owned public company client ID')
13
+ authorization=trusted_https(config['authorizationEndpoint']);token=trusted_https(config['tokenEndpoint'])
14
+ scopes=config['scopes']
15
+ if not isinstance(scopes,list)or not 3<=len(scopes)<=8 or any(not isinstance(scope,str)or not 1<=len(scope)<=300 or any(c.isspace()for c in scope)for scope in scopes)or len(set(scopes))!=len(scopes)or not {'openid','profile'}.issubset(scopes)or 'offline_access'in scopes or not any(scope==identity.scope or scope.endswith('/'+identity.scope)for scope in scopes):raise ValueError('Use OIDC and the owned delegated API scope without browser refresh credentials')
16
+ return {'issuer':identity.issuer,'clientId':config['clientId'],'authorizationEndpoint':authorization,'tokenEndpoint':token,'scopes':list(scopes)}
17
+
18
+
19
+ def install_web(app,identity,connections,config,static_root=None):
20
+ company=company_configuration(identity,config)
21
+ token=company['tokenEndpoint']
22
+ root=Path(static_root)if static_root is not None else Path(__file__).parent
23
+ lock=json.loads((root/'enterprise-web-lock.json').read_text())
24
+ if hashlib.sha256((root/'enterprise-web-app.js').read_bytes()).hexdigest()!=lock['bundleSha256']:raise ValueError('Use the exact reviewed browser bundle')
25
+ # Native source constructors already validate these tenant citation hosts.
26
+ hosts=sorted({entry.driver.host for entry in connections.values()})
27
+ public={'company':company,'citationHosts':hosts,'authorizationOrigins':['https://login.microsoftonline.com','https://auth.atlassian.com']}
28
+ @app.get('/api/web-config')
29
+ def web_config():return public
30
+ @app.get('/')
31
+ @app.get('/company-callback')
32
+ def index():return FileResponse(root/'enterprise-web.html',media_type='text/html')
33
+ @app.get('/assets/workspace.js')
34
+ def javascript():return FileResponse(root/'enterprise-web-app.js',media_type='application/javascript')
35
+ @app.get('/assets/workspace.css')
36
+ def stylesheet():return FileResponse(root/'enterprise-web.css',media_type='text/css')
37
+ @app.get('/assets/notices.txt')
38
+ def notices():return FileResponse(root/'enterprise-web-notices.txt',media_type='text/plain')
39
+ return "default-src 'self'; script-src 'self'; style-src 'self'; connect-src 'self' "+urlsplit(token).scheme+'://'+urlsplit(token).netloc+"; frame-ancestors 'none'; base-uri 'none'; form-action 'self'"