pz-cli 5.0.0__py3-none-any.whl

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 (399) hide show
  1. pz_cli-5.0.0.dist-info/METADATA +51 -0
  2. pz_cli-5.0.0.dist-info/RECORD +399 -0
  3. pz_cli-5.0.0.dist-info/WHEEL +4 -0
  4. pz_cli-5.0.0.dist-info/entry_points.txt +5 -0
  5. pz_core/__init__.py +64 -0
  6. pz_core/__main__.py +7 -0
  7. pz_core/app.py +45 -0
  8. pz_core/assembly/__init__.py +17 -0
  9. pz_core/assembly/app_discovery.py +1240 -0
  10. pz_core/assembly/apps/__init__.py +25 -0
  11. pz_core/assembly/apps/compiler.py +824 -0
  12. pz_core/assembly/apps/providers.py +188 -0
  13. pz_core/assembly/cli/__init__.py +9 -0
  14. pz_core/assembly/cli/compiler.py +166 -0
  15. pz_core/assembly/diagnostics/__init__.py +21 -0
  16. pz_core/assembly/diagnostics/checks.py +257 -0
  17. pz_core/assembly/diagnostics/dev_sources.py +569 -0
  18. pz_core/assembly/events/__init__.py +5 -0
  19. pz_core/assembly/events/compiler.py +219 -0
  20. pz_core/assembly/extensions/__init__.py +5 -0
  21. pz_core/assembly/extensions/compiler.py +696 -0
  22. pz_core/assembly/infra/__init__.py +6 -0
  23. pz_core/assembly/infra/compiler.py +272 -0
  24. pz_core/assembly/lifecycle/__init__.py +5 -0
  25. pz_core/assembly/lifecycle/compiler.py +95 -0
  26. pz_core/assembly/middlewares/__init__.py +19 -0
  27. pz_core/assembly/middlewares/compiler.py +508 -0
  28. pz_core/assembly/project/__init__.py +7 -0
  29. pz_core/assembly/project/compiler.py +9 -0
  30. pz_core/assembly/project/context.py +17 -0
  31. pz_core/assembly/project/discovery.py +227 -0
  32. pz_core/assembly/resources/__init__.py +19 -0
  33. pz_core/assembly/resources/compiler.py +310 -0
  34. pz_core/assembly/resources/settings.py +129 -0
  35. pz_core/assembly/routes/__init__.py +7 -0
  36. pz_core/assembly/routes/compiler.py +400 -0
  37. pz_core/assembly/runtime_manifests/__init__.py +13 -0
  38. pz_core/assembly/runtime_manifests/compiler.py +159 -0
  39. pz_core/assembly/settings/__init__.py +17 -0
  40. pz_core/assembly/settings/compiler.py +786 -0
  41. pz_core/assembly/settings/host_project_overrides.py +82 -0
  42. pz_core/assembly/settings/host_runtime_endpoint.py +207 -0
  43. pz_core/authoring/__init__.py +79 -0
  44. pz_core/authoring/app_init_service.py +946 -0
  45. pz_core/authoring/app_versions.py +814 -0
  46. pz_core/authoring/cli.py +192 -0
  47. pz_core/authoring/commands.py +1810 -0
  48. pz_core/authoring/conflicts.py +180 -0
  49. pz_core/authoring/dev_bindings.py +916 -0
  50. pz_core/authoring/dev_git.py +1087 -0
  51. pz_core/authoring/dev_import.py +1061 -0
  52. pz_core/authoring/dev_manifest.py +522 -0
  53. pz_core/authoring/dev_sources.py +1638 -0
  54. pz_core/authoring/env_cli.py +454 -0
  55. pz_core/authoring/files.py +109 -0
  56. pz_core/authoring/ide_projection.py +1129 -0
  57. pz_core/authoring/naming.py +58 -0
  58. pz_core/authoring/official_dev_sources.py +432 -0
  59. pz_core/authoring/official_sources.json +156 -0
  60. pz_core/authoring/orchestration_service.py +558 -0
  61. pz_core/authoring/project_default.jsonc +53 -0
  62. pz_core/authoring/project_defaults.py +88 -0
  63. pz_core/authoring/project_metadata_service.py +199 -0
  64. pz_core/authoring/project_settings_checks.py +51 -0
  65. pz_core/authoring/project_settings_cli.py +299 -0
  66. pz_core/authoring/project_settings_service.py +1700 -0
  67. pz_core/authoring/project_state.py +399 -0
  68. pz_core/authoring/publish_git.py +423 -0
  69. pz_core/authoring/publish_lock.py +188 -0
  70. pz_core/authoring/publish_source.py +195 -0
  71. pz_core/authoring/release.py +1382 -0
  72. pz_core/authoring/repo_keys.py +29 -0
  73. pz_core/authoring/root_cli.py +28 -0
  74. pz_core/authoring/scaffold_service.py +325 -0
  75. pz_core/authoring/sync_service.py +787 -0
  76. pz_core/authoring/templates/app/__init__.py +1 -0
  77. pz_core/authoring/templates/app/api.py +25 -0
  78. pz_core/authoring/templates/app/apps.py +31 -0
  79. pz_core/authoring/templates/app/auth_package_init.py +14 -0
  80. pz_core/authoring/templates/app/auth_readme.py +29 -0
  81. pz_core/authoring/templates/app/configs_package_init.py +14 -0
  82. pz_core/authoring/templates/app/default_env.py +18 -0
  83. pz_core/authoring/templates/app/default_jsonc.py +25 -0
  84. pz_core/authoring/templates/app/locales_readme.py +25 -0
  85. pz_core/authoring/templates/app/migrations_readme.py +19 -0
  86. pz_core/authoring/templates/app/models.py +14 -0
  87. pz_core/authoring/templates/app/package_init.py +8 -0
  88. pz_core/authoring/templates/app/pages_base.py +161 -0
  89. pz_core/authoring/templates/app/pages_readme.py +25 -0
  90. pz_core/authoring/templates/app/public.py +31 -0
  91. pz_core/authoring/templates/app/pyproject.py +77 -0
  92. pz_core/authoring/templates/app/readme.py +74 -0
  93. pz_core/authoring/templates/app/settings.py +46 -0
  94. pz_core/authoring/templates/app/static_app_js.py +24 -0
  95. pz_core/authoring/templates/app/static_readme.py +31 -0
  96. pz_core/authoring/templates/app/template_config_page.py +23 -0
  97. pz_core/authoring/templates/app/template_index_html.py +28 -0
  98. pz_core/authoring/templates/app/templates_readme.py +32 -0
  99. pz_core/authoring/templates/app/urls.py +36 -0
  100. pz_core/authoring/templates/app/views.py +36 -0
  101. pz_core/authoring/templates/project/__init__.py +1 -0
  102. pz_core/authoring/templates/project/agents.py +51 -0
  103. pz_core/authoring/templates/project/apps_container_readme.py +37 -0
  104. pz_core/authoring/templates/project/main_py.py +59 -0
  105. pz_core/authoring/templates/project/project_env.py +19 -0
  106. pz_core/authoring/templates/project/pyproject.py +26 -0
  107. pz_core/authoring/templates/project/pz_env_env.py +65 -0
  108. pz_core/authoring/templates/project/pz_env_jsonc.py +69 -0
  109. pz_core/authoring/templates/project/pz_jsonc.py +12 -0
  110. pz_core/authoring/templates/project/readme.py +94 -0
  111. pz_core/authoring/templates/project/root_gitkeep.py +8 -0
  112. pz_core/authoring/templates/shared/__init__.py +5 -0
  113. pz_core/authoring/templates/shared/text.py +10 -0
  114. pz_core/authoring/venv_service.py +87 -0
  115. pz_core/authoring/workspace.py +167 -0
  116. pz_core/cli.py +291 -0
  117. pz_core/cli_options.py +74 -0
  118. pz_core/core/__init__.py +3 -0
  119. pz_core/core/errors/__init__.py +5 -0
  120. pz_core/core/errors/models.py +117 -0
  121. pz_core/core/events/__init__.py +5 -0
  122. pz_core/core/events/registry.py +54 -0
  123. pz_core/core/extensions/__init__.py +51 -0
  124. pz_core/core/extensions/registry.py +790 -0
  125. pz_core/core/health/__init__.py +6 -0
  126. pz_core/core/health/contracts.py +10 -0
  127. pz_core/core/middlewares/__init__.py +6 -0
  128. pz_core/core/middlewares/models.py +24 -0
  129. pz_core/core/routing/__init__.py +5 -0
  130. pz_core/core/routing/models.py +21 -0
  131. pz_core/core/settings_contracts.py +25 -0
  132. pz_core/docs/README.md +20 -0
  133. pz_core/docs/distribution-README.md +438 -0
  134. pz_core/docs/pz/345/221/275/344/273/244/346/200/273/350/247/210.md +1011 -0
  135. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/00-pz/346/236/266/346/236/204/346/200/273/347/272/262.md +240 -0
  136. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/01-/347/233/256/345/275/225/344/270/216/345/210/206/345/261/202/350/247/204/350/214/203.md +216 -0
  137. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/02-/345/214/205/344/270/216/345/210/206/345/217/221/350/247/204/350/214/203.md +403 -0
  138. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/03-App/346/234/200/345/260/217/345/255/220/345/214/205/350/247/204/350/214/203.md +571 -0
  139. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/04-Settings/344/270/216/351/205/215/347/275/256/350/247/204/350/214/203.md +1177 -0
  140. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/05-/350/265/204/346/272/220/344/270/216/350/277/220/350/241/214/347/233/256/345/275/225/350/247/204/350/214/203.md +575 -0
  141. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/06-/346/225/260/346/215/256/345/272/223/344/270/216/345/217/257/351/200/211/345/237/272/347/241/200/350/256/276/346/226/275/350/247/204/350/214/203.md +499 -0
  142. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/07-App/346/233/277/346/215/242/350/247/204/350/214/203.md +206 -0
  143. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/08-/345/271/263/345/217/260/347/272/247App/344/270/216/350/277/220/350/241/214/346/227/266/345/215/225/344/276/213/350/247/204/350/214/203.md +445 -0
  144. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/09-CLI/344/270/216Worker/345/205/245/345/217/243/350/247/204/350/214/203.md +950 -0
  145. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/10-/346/265/213/350/257/225/344/270/216/345/245/221/347/272/246/350/247/204/350/214/203.md +1001 -0
  146. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/11-/344/273/223/345/272/223/347/234/237/347/233/270/346/272/220/345/234/260/345/233/276.md +102 -0
  147. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/12-/346/236/204/345/273/272/344/270/216/345/217/221/345/270/203/346/243/200/346/237/245/346/270/205/345/215/225.md +171 -0
  148. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/13-/346/255/243/345/274/217Facade/346/200/273/350/241/250.md +402 -0
  149. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/14-/345/210/206/345/261/202/344/270/216/346/211/251/345/261/225/350/247/204/350/214/203.md +550 -0
  150. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/15-/345/255/220/345/214/205/345/274/200/345/217/221/344/275/277/347/224/250/346/211/213/345/206/214.md +1004 -0
  151. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/16-/346/211/251/345/261/225/347/202/271/346/255/243/345/274/217/345/215/217/350/256/256/346/270/205/345/215/225.md +352 -0
  152. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/17-pz.protocols.v1/346/255/243/345/274/217/345/215/217/350/256/256.md +266 -0
  153. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/18-/346/211/251/345/261/225/347/202/271/345/206/262/347/252/201/350/243/201/345/206/263/346/200/273/350/241/250.md +131 -0
  154. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/19-/351/224/231/350/257/257/347/240/201/344/270/216/350/257/212/346/226/255/345/215/217/350/256/256.md +176 -0
  155. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/20-/347/247/201/346/234/211/345/214/205/344/273/223/344/270/216/347/273/237/344/270/200/350/256/244/350/257/201/351/203/250/347/275/262/350/247/204/350/214/203.md +243 -0
  156. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/21-contrib/347/272/246/345/256/232/344/270/216Provider/346/216/245/345/205/245/350/247/204/350/214/203.md +365 -0
  157. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/22-/345/244/232/345/233/275/350/257/255/350/250/200/350/265/204/346/272/220/346/262/273/347/220/206/344/270/216/346/214/211/351/234/200/345/212/240/350/275/275/350/247/204/350/214/203.md +912 -0
  158. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/23-/351/246/226/345/261/217/345/261/225/347/244/272/344/270/216/351/246/226/345/270/247/347/250/263/345/256/232/350/247/204/350/214/203.md +269 -0
  159. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/24-pz/345/220/257/345/212/250/344/270/216/350/277/220/350/241/214/346/227/266/347/274/226/350/257/221/346/265/201/347/250/213.md +245 -0
  160. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/25-/351/235/242/345/220/221/345/257/271/350/261/241/345/274/200/345/217/221/350/247/204/350/214/203.md +300 -0
  161. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/README.md +98 -0
  162. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/00-legacy_core/346/240/207/345/207/206pip/345/214/205/345/214/226/351/207/215/346/236/204/346/200/273/347/272/262.md +1073 -0
  163. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/01-/347/233/256/345/275/225/344/270/216/345/210/206/345/261/202/351/207/215/346/236/204/346/211/247/350/241/214/346/226/271/346/241/210.md +224 -0
  164. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/02-/345/214/205/344/270/216/345/210/206/345/217/221/350/247/204/350/214/203-/345/220/253/345/216/206/345/217/262/345/217/243/345/276/204.md +671 -0
  165. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/13-/344/270/200/346/254/241/346/200/247/345/256/236/346/226/275/346/270/205/345/215/225.md +387 -0
  166. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/14-/345/255/220/345/214/205/351/200/202/351/205/215/346/224/271/351/200/240/346/270/205/345/215/225.md +592 -0
  167. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/15-infra/350/243/201/345/211/252/344/270/216/350/220/275/344/275/215/346/270/205/345/215/225.md +81 -0
  168. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/19-core/345/215/225/344/270/200/346/255/243/345/274/217/346/226/271/346/241/210/346/224/266/346/225/233/346/211/247/350/241/214/346/270/205/345/215/225.md +161 -0
  169. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/25-/345/215/217/350/256/256/345/210/260/346/272/220/347/240/201/345/267/256/350/267/235/346/270/205/345/215/225.md +377 -0
  170. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/26-cutover/345/256/214/345/267/245/346/243/200/346/237/245/346/270/205/345/215/225.md +117 -0
  171. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/29-/345/244/232/345/233/275/350/257/255/350/250/200/350/265/204/346/272/220/346/262/273/347/220/206/344/270/216/346/214/211/351/234/200/345/212/240/350/275/275/350/247/204/350/214/203-/345/220/253/350/277/201/347/247/273/347/255/226/347/225/245.md +956 -0
  172. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/99-/350/265/204/346/272/220/346/224/266/345/217/243/345/220/216/347/273/255TODO.md +84 -0
  173. pz_core/docs//345/217/221/347/211/210/345/211/215/346/270/205/345/215/225-4.0.11.md +84 -0
  174. pz_core/docs//346/226/260/346/211/213/346/214/207/345/215/227.md +422 -0
  175. pz_core/infra/__init__.py +17 -0
  176. pz_core/infra/db/__init__.py +23 -0
  177. pz_core/infra/db/contracts.py +73 -0
  178. pz_core/infra/health/__init__.py +5 -0
  179. pz_core/infra/health/contracts.py +8 -0
  180. pz_core/infra/kv/__init__.py +5 -0
  181. pz_core/infra/kv/contracts.py +17 -0
  182. pz_core/infra/observability/__init__.py +23 -0
  183. pz_core/infra/observability/contracts.py +142 -0
  184. pz_core/infra/resilience/__init__.py +5 -0
  185. pz_core/infra/resilience/contracts.py +17 -0
  186. pz_core/infra/resources/__init__.py +5 -0
  187. pz_core/infra/resources/contracts.py +16 -0
  188. pz_core/infra/settings/__init__.py +17 -0
  189. pz_core/infra/settings/models.py +81 -0
  190. pz_core/infra/storage/__init__.py +5 -0
  191. pz_core/infra/storage/ports.py +27 -0
  192. pz_core/project_root_commands.py +282 -0
  193. pz_core/project_runtime_cli.py +344 -0
  194. pz_core/project_sources.py +253 -0
  195. pz_core/pz_launcher.py +209 -0
  196. pz_core/root_cli.py +70 -0
  197. pz_core/runtime/__init__.py +229 -0
  198. pz_core/runtime/access.py +86 -0
  199. pz_core/runtime/adapters/__init__.py +5 -0
  200. pz_core/runtime/adapters/base.py +18 -0
  201. pz_core/runtime/asyncio_bridge.py +18 -0
  202. pz_core/runtime/bindings/__init__.py +5 -0
  203. pz_core/runtime/bindings/container.py +50 -0
  204. pz_core/runtime/cli/__init__.py +5 -0
  205. pz_core/runtime/cli/_args.py +67 -0
  206. pz_core/runtime/cli/app_commands.py +417 -0
  207. pz_core/runtime/cli/check.py +214 -0
  208. pz_core/runtime/cli/db.py +297 -0
  209. pz_core/runtime/cli/main.py +89 -0
  210. pz_core/runtime/cli/resources.py +280 -0
  211. pz_core/runtime/discovery.py +23 -0
  212. pz_core/runtime/environment_integrity.py +157 -0
  213. pz_core/runtime/errors.py +5 -0
  214. pz_core/runtime/events.py +87 -0
  215. pz_core/runtime/facades/__init__.py +38 -0
  216. pz_core/runtime/facades/_capabilities.py +46 -0
  217. pz_core/runtime/facades/cache.py +103 -0
  218. pz_core/runtime/facades/database.py +71 -0
  219. pz_core/runtime/facades/events.py +380 -0
  220. pz_core/runtime/facades/health.py +101 -0
  221. pz_core/runtime/facades/mail.py +121 -0
  222. pz_core/runtime/facades/observability.py +102 -0
  223. pz_core/runtime/facades/providers.py +75 -0
  224. pz_core/runtime/facades/resources.py +52 -0
  225. pz_core/runtime/facades/storage.py +296 -0
  226. pz_core/runtime/health/__init__.py +5 -0
  227. pz_core/runtime/health/service.py +102 -0
  228. pz_core/runtime/host.py +188 -0
  229. pz_core/runtime/host_runner.py +282 -0
  230. pz_core/runtime/http/__init__.py +20 -0
  231. pz_core/runtime/http/app.py +576 -0
  232. pz_core/runtime/http_timing.py +173 -0
  233. pz_core/runtime/invocation.py +29 -0
  234. pz_core/runtime/json_safe.py +47 -0
  235. pz_core/runtime/lifecycle/__init__.py +25 -0
  236. pz_core/runtime/lifecycle/manager.py +533 -0
  237. pz_core/runtime/plan.py +214 -0
  238. pz_core/runtime/plan_context.py +33 -0
  239. pz_core/runtime/plan_snapshot.py +177 -0
  240. pz_core/runtime/registries/__init__.py +27 -0
  241. pz_core/runtime/registries/capabilities.py +101 -0
  242. pz_core/runtime/registries/providers.py +396 -0
  243. pz_core/runtime/resource_assets.py +344 -0
  244. pz_core/runtime/run_cli.py +211 -0
  245. pz_core/runtime/settings.py +888 -0
  246. pz_core/runtime/worker/__init__.py +21 -0
  247. pz_core/runtime/worker/engine.py +921 -0
  248. pz_core/runtime/worker/runtime.py +25 -0
  249. pz_core/testing/__init__.py +15 -0
  250. pz_core/testing/doc_route_contracts.py +305 -0
  251. pz_core/utils/__init__.py +44 -0
  252. pz_core/utils/env.py +65 -0
  253. pz_core/utils/project_jsonc/__init__.py +29 -0
  254. pz_core/utils/project_jsonc/document.py +420 -0
  255. pz_core/utils/project_settings.py +99 -0
  256. pz_foundation/__init__.py +50 -0
  257. pz_foundation/_contracts.py +81 -0
  258. pz_foundation/_errors.py +34 -0
  259. pz_foundation/api.py +14 -0
  260. pz_foundation/apps.py +127 -0
  261. pz_foundation/auth/__init__.py +1 -0
  262. pz_foundation/cache_control.py +233 -0
  263. pz_foundation/cli/__init__.py +2 -0
  264. pz_foundation/cli/workers.py +565 -0
  265. pz_foundation/configs/__init__.py +0 -0
  266. pz_foundation/configs/default.env +15 -0
  267. pz_foundation/configs/default.jsonc +452 -0
  268. pz_foundation/db/__init__.py +94 -0
  269. pz_foundation/db/aerich_executor.py +664 -0
  270. pz_foundation/db/config.py +358 -0
  271. pz_foundation/db/connection_manager.py +689 -0
  272. pz_foundation/db/connection_probe.py +204 -0
  273. pz_foundation/db/database.py +1291 -0
  274. pz_foundation/db/deps.py +47 -0
  275. pz_foundation/db/lifecycle_status.py +85 -0
  276. pz_foundation/db/migrations.py +477 -0
  277. pz_foundation/db/mysql.py +57 -0
  278. pz_foundation/db/postgresql.py +409 -0
  279. pz_foundation/db/production_pool_fallback.py +143 -0
  280. pz_foundation/db/public.py +45 -0
  281. pz_foundation/db/session.py +369 -0
  282. pz_foundation/db/sqlite.py +15 -0
  283. pz_foundation/db/tortoise_manager.py +724 -0
  284. pz_foundation/db/tortoise_psycopg.py +148 -0
  285. pz_foundation/docs/README.md +18 -0
  286. pz_foundation/docs/distribution-README.md +319 -0
  287. pz_foundation/docs/infra/350/243/201/345/211/252/344/270/216/350/220/275/344/275/215/350/257/264/346/230/216.md +39 -0
  288. pz_foundation/docs//345/205/254/345/274/200/345/205/215/350/264/271/346/216/245/345/217/243/351/230/262/346/273/245/347/224/250/344/270/216/344/272/272/346/234/272/346/240/241/351/252/214.md +680 -0
  289. pz_foundation/docs//350/256/276/350/256/241/346/226/207/346/241/243.md +288 -0
  290. pz_foundation/docs//350/267/257/347/224/261/344/270/216URL/345/244/204/347/220/206/350/247/204/350/214/203.md +119 -0
  291. pz_foundation/health.py +105 -0
  292. pz_foundation/host/__init__.py +11 -0
  293. pz_foundation/host/builder.py +34 -0
  294. pz_foundation/host/cache_runtime.py +44 -0
  295. pz_foundation/host/db/__init__.py +17 -0
  296. pz_foundation/host/db/aerich_config.py +36 -0
  297. pz_foundation/host/db/config.py +106 -0
  298. pz_foundation/host/db/connection_manager.py +48 -0
  299. pz_foundation/host/db/deps.py +5 -0
  300. pz_foundation/host/db/orm_retry_patch.py +566 -0
  301. pz_foundation/host/db/request_adapter.py +325 -0
  302. pz_foundation/host/db/router.py +28 -0
  303. pz_foundation/host/db/session.py +284 -0
  304. pz_foundation/host/db/tortoise_manager.py +10 -0
  305. pz_foundation/host/idempotency.py +132 -0
  306. pz_foundation/host/lifespan.py +247 -0
  307. pz_foundation/host/redis_runtime.py +100 -0
  308. pz_foundation/host/web.py +790 -0
  309. pz_foundation/infra_provider.py +236 -0
  310. pz_foundation/kv/__init__.py +25 -0
  311. pz_foundation/kv/cache.py +1166 -0
  312. pz_foundation/kv/deps.py +84 -0
  313. pz_foundation/kv/public.py +25 -0
  314. pz_foundation/kv/redis_manager.py +1531 -0
  315. pz_foundation/lazy_exports.py +43 -0
  316. pz_foundation/lazy_imports.py +124 -0
  317. pz_foundation/lifecycle.py +392 -0
  318. pz_foundation/managed_workers.py +1117 -0
  319. pz_foundation/middlewares/__init__.py +26 -0
  320. pz_foundation/middlewares/_settings.py +250 -0
  321. pz_foundation/middlewares/analytics.py +124 -0
  322. pz_foundation/middlewares/cache_control.py +156 -0
  323. pz_foundation/middlewares/compression.py +202 -0
  324. pz_foundation/middlewares/cors.py +62 -0
  325. pz_foundation/middlewares/geo_country.py +44 -0
  326. pz_foundation/middlewares/inner_router.py +50 -0
  327. pz_foundation/middlewares/rate_limited/__init__.py +3 -0
  328. pz_foundation/middlewares/rate_limited/key_limiter.py +115 -0
  329. pz_foundation/middlewares/request_logging.py +104 -0
  330. pz_foundation/middlewares/route_owner.py +309 -0
  331. pz_foundation/middlewares/security_csrf.py +44 -0
  332. pz_foundation/middlewares/security_headers/__init__.py +4 -0
  333. pz_foundation/middlewares/security_headers/conflicts.py +44 -0
  334. pz_foundation/middlewares/security_headers/csp_nonce.py +200 -0
  335. pz_foundation/middlewares/security_headers/security_headers.py +44 -0
  336. pz_foundation/migrations/__init__.py +1 -0
  337. pz_foundation/migrations/pz_foundation/__init__.py +1 -0
  338. pz_foundation/models.py +22 -0
  339. pz_foundation/notifications/__init__.py +23 -0
  340. pz_foundation/notifications/email.py +542 -0
  341. pz_foundation/notifications/public.py +31 -0
  342. pz_foundation/observability/__init__.py +71 -0
  343. pz_foundation/observability/http.py +34 -0
  344. pz_foundation/observability/logger/__init__.py +49 -0
  345. pz_foundation/observability/logger/_bot_settings.py +126 -0
  346. pz_foundation/observability/logger/bot_message.py +592 -0
  347. pz_foundation/observability/logger/core.py +385 -0
  348. pz_foundation/observability/logger/delivery_audit.py +84 -0
  349. pz_foundation/observability/logger/feishu_bot.py +199 -0
  350. pz_foundation/observability/logger/telegram_bot.py +194 -0
  351. pz_foundation/observability/metrics.py +365 -0
  352. pz_foundation/observability/runtime.py +68 -0
  353. pz_foundation/public.py +405 -0
  354. pz_foundation/resources/__init__.py +12 -0
  355. pz_foundation/resources/builder.py +900 -0
  356. pz_foundation/resources/images.py +363 -0
  357. pz_foundation/root_cli.py +20 -0
  358. pz_foundation/routing/__init__.py +203 -0
  359. pz_foundation/routing/canonical.py +486 -0
  360. pz_foundation/routing/convertors.py +82 -0
  361. pz_foundation/routing/declarations.py +843 -0
  362. pz_foundation/routing/exports.py +148 -0
  363. pz_foundation/routing/loaders.py +138 -0
  364. pz_foundation/routing/manifest.py +215 -0
  365. pz_foundation/routing/manifest_provider.py +47 -0
  366. pz_foundation/routing/mounts.py +101 -0
  367. pz_foundation/routing/ordering.py +172 -0
  368. pz_foundation/routing/registry.py +133 -0
  369. pz_foundation/routing/utils.py +28 -0
  370. pz_foundation/runtime.py +234 -0
  371. pz_foundation/settings.py +253 -0
  372. pz_foundation/settings_normalization.py +130 -0
  373. pz_foundation/storage/__init__.py +25 -0
  374. pz_foundation/storage/local.py +209 -0
  375. pz_foundation/storage/public.py +106 -0
  376. pz_foundation/storage/s3.py +116 -0
  377. pz_foundation/storage/service.py +234 -0
  378. pz_foundation/urls.py +19 -0
  379. pz_foundation/utils/__init__.py +6 -0
  380. pz_foundation/utils/merge.py +160 -0
  381. pz_foundation/worker_replicas.py +55 -0
  382. pz_settings_center/__init__.py +5 -0
  383. pz_settings_center/api/__init__.py +5 -0
  384. pz_settings_center/api/runtime_overrides.py +360 -0
  385. pz_settings_center/apps.py +28 -0
  386. pz_settings_center/configs/__init__.py +0 -0
  387. pz_settings_center/configs/default.env +2 -0
  388. pz_settings_center/configs/default.jsonc +1 -0
  389. pz_settings_center/docs/01-settings_center/350/203/275/345/212/233/350/276/271/347/225/214.md +124 -0
  390. pz_settings_center/docs/README.md +12 -0
  391. pz_settings_center/docs/distribution-README.md +150 -0
  392. pz_settings_center/docs//350/256/276/350/256/241/346/226/207/346/241/243.md +48 -0
  393. pz_settings_center/integration_payload.py +18 -0
  394. pz_settings_center/project_helpers.py +405 -0
  395. pz_settings_center/public.py +139 -0
  396. pz_settings_center/routing_helpers.py +73 -0
  397. pz_settings_center/runtime_override_provider.py +61 -0
  398. pz_settings_center/typed_settings.py +324 -0
  399. pz_settings_center/urls.py +9 -0
@@ -0,0 +1,790 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from importlib import import_module
5
+ import inspect
6
+ from typing import Any, TypeAlias
7
+
8
+ from pz_core.core.errors import PzError
9
+
10
+ WORKER_ROLE_KINDS = frozenset(
11
+ {
12
+ "infrastructure_worker",
13
+ "operator_ingress_worker",
14
+ "resident_actor_worker",
15
+ "task_driven_actor_worker",
16
+ }
17
+ )
18
+
19
+
20
+ def _resolve_dotted(target: str) -> Any:
21
+ module_name, attr_name = str(target or "").rsplit(".", 1)
22
+ module = import_module(module_name)
23
+ return getattr(module, attr_name)
24
+
25
+
26
+ def _instantiate_extension_target(target: Any) -> Any:
27
+ if isinstance(target, str):
28
+ target = _resolve_dotted(target)
29
+ if inspect.isclass(target):
30
+ return target()
31
+ return target
32
+
33
+
34
+ @dataclass(frozen=True, slots=True)
35
+ class ExtensionSpec:
36
+ id: str
37
+ kind: str
38
+ owner_app: str
39
+ api_version: str = "v1"
40
+ priority: int = 100
41
+ title: str = ""
42
+ payload: dict[str, Any] = field(default_factory=dict)
43
+ source: str = ""
44
+ provides: tuple[str, ...] = ()
45
+ conflicts: tuple[str, ...] = ()
46
+
47
+
48
+ def _is_declarative_payload_value(value: Any) -> bool:
49
+ if value is None or isinstance(value, (str, int, float, bool)):
50
+ return True
51
+ if isinstance(value, (list, tuple)):
52
+ return all(_is_declarative_payload_value(item) for item in value)
53
+ if isinstance(value, dict):
54
+ return all(
55
+ isinstance(key, str) and _is_declarative_payload_value(item)
56
+ for key, item in value.items()
57
+ )
58
+ return False
59
+
60
+
61
+ @dataclass(frozen=True, slots=True)
62
+ class HostBuildContext:
63
+ project: Any
64
+ plan: Any
65
+ settings: Any
66
+ bindings: Any
67
+ lifecycle_state: Any | None = None
68
+
69
+
70
+ @dataclass(frozen=True, slots=True)
71
+ class HostBuildResult:
72
+ app: Any
73
+ app_kind: str = "asgi"
74
+ health_path: str | None = None
75
+
76
+
77
+ @dataclass(frozen=True, slots=True)
78
+ class HostBuilderSpec:
79
+ id: str
80
+ owner_app: str
81
+ builder: Any
82
+ kind: str = "host.builder"
83
+ runtime_kind: str = "web"
84
+ api_version: str = "v1"
85
+ priority: int = 100
86
+ title: str = ""
87
+
88
+
89
+ @dataclass(frozen=True, slots=True)
90
+ class SettingsPatch:
91
+ app_id: str
92
+ payload: dict[str, Any]
93
+ source: str
94
+ priority: int = 100
95
+
96
+
97
+ @dataclass(frozen=True, slots=True)
98
+ class SettingsProviderContext:
99
+ project: Any
100
+ snapshot: Any
101
+ settings: Any
102
+
103
+
104
+ @dataclass(frozen=True, slots=True)
105
+ class SettingsProviderSpec:
106
+ id: str
107
+ owner_app: str
108
+ provider: Any
109
+ kind: str = "settings.provider"
110
+ api_version: str = "v1"
111
+ priority: int = 100
112
+
113
+
114
+ @dataclass(frozen=True, slots=True)
115
+ class InfraPatch:
116
+ component: str
117
+ payload: dict[str, Any]
118
+ source: str
119
+ priority: int = 100
120
+
121
+
122
+ @dataclass(frozen=True, slots=True)
123
+ class InfraProviderContext:
124
+ project: Any
125
+ snapshot: Any
126
+
127
+
128
+ @dataclass(frozen=True, slots=True)
129
+ class InfraProviderSpec:
130
+ id: str
131
+ owner_app: str
132
+ provider: Any
133
+ kind: str = "infra.provider"
134
+ api_version: str = "v1"
135
+ priority: int = 100
136
+ title: str = ""
137
+
138
+
139
+ @dataclass(frozen=True, slots=True)
140
+ class RuntimeManifestContributionSpec:
141
+ id: str
142
+ owner_app: str
143
+ provider: Any
144
+ kind: str = "runtime.manifest_contribution"
145
+ api_version: str = "v1"
146
+ priority: int = 100
147
+ title: str = ""
148
+
149
+
150
+ @dataclass(frozen=True, slots=True)
151
+ class LifecycleContributionSpec:
152
+ id: str
153
+ owner_app: str
154
+ handler: Any
155
+ phase: str
156
+ kind: str = "lifecycle.contribution"
157
+ api_version: str = "v1"
158
+ priority: int = 100
159
+
160
+
161
+ @dataclass(frozen=True, slots=True)
162
+ class WorkerRoleSpec:
163
+ id: str
164
+ owner_app: str
165
+ tool_name: str
166
+ kind: str = "worker.role"
167
+ api_version: str = "v1"
168
+ priority: int = 100
169
+ title: str = ""
170
+ name: str = ""
171
+ auto_start: bool = False
172
+ auto_start_setting: str = ""
173
+ restart: str = "always"
174
+ worker_kind: str = "infrastructure_worker"
175
+ replica_count_setting: str = ""
176
+ description: str = ""
177
+
178
+
179
+ @dataclass(frozen=True, slots=True)
180
+ class CliInvocationContext:
181
+ project: Any
182
+ args: Any
183
+ stdout: Any
184
+ stderr: Any
185
+ plan: Any | None = None
186
+ app_id: str = ""
187
+ tool_name: str = ""
188
+ argv: tuple[str, ...] = ()
189
+ remainder: tuple[str, ...] = ()
190
+
191
+
192
+ @dataclass(frozen=True, slots=True)
193
+ class CliToolSpec:
194
+ id: str
195
+ owner_app: str
196
+ command: tuple[str, ...]
197
+ summary: str
198
+ tool: Any
199
+ kind: str = "cli.tool"
200
+ api_version: str = "v1"
201
+ priority: int = 100
202
+ title: str = ""
203
+
204
+
205
+ @dataclass(frozen=True, slots=True)
206
+ class PageProfileProviderSpec:
207
+ id: str
208
+ owner_app: str
209
+ provider: Any
210
+ kind: str = "page.profile_provider"
211
+ api_version: str = "v1"
212
+ priority: int = 100
213
+ title: str = ""
214
+
215
+
216
+ @dataclass(frozen=True, slots=True)
217
+ class ResourceContributionSpec:
218
+ id: str
219
+ owner_app: str
220
+ templates_dir: str = "templates"
221
+ static_dir: str = "static"
222
+ locales_dir: str = "locales"
223
+ migrations_dir: str = "migrations"
224
+ kind: str = "resource.contribution"
225
+ api_version: str = "v1"
226
+ priority: int = 100
227
+ title: str = ""
228
+ locale_bundles: tuple[dict[str, Any], ...] = ()
229
+ locale_dependencies: tuple[dict[str, Any], ...] = ()
230
+ locale_consumer_bindings: tuple[dict[str, Any], ...] = ()
231
+ locale_profiles: tuple[dict[str, Any], ...] = ()
232
+ locale_overrides: tuple[dict[str, Any], ...] = ()
233
+ locale_allowlist: tuple[str, ...] = ()
234
+
235
+
236
+ @dataclass(frozen=True, slots=True)
237
+ class ProtocolRegistrationSpec:
238
+ id: str
239
+ owner_app: str
240
+ protocol_kind: str
241
+ spec_name: str
242
+ runtime_owner: str
243
+ required_fields: tuple[str, ...] = ()
244
+ string_fields: tuple[str, ...] = ()
245
+ import_fields: tuple[str, ...] = ()
246
+ callable_fields: tuple[str, ...] = ()
247
+ validator: Any | None = None
248
+ kind: str = "protocol.registration"
249
+ api_version: str = "v1"
250
+ priority: int = 100
251
+ title: str = ""
252
+
253
+
254
+ @dataclass(frozen=True, slots=True)
255
+ class ProtocolExtensionSpec:
256
+ id: str
257
+ owner_app: str
258
+ kind: str
259
+ fields: dict[str, Any] = field(default_factory=dict)
260
+ optional: bool = False
261
+ api_version: str = "v1"
262
+ priority: int = 100
263
+ title: str = ""
264
+
265
+
266
+ FormalExtensionSpec: TypeAlias = (
267
+ ExtensionSpec
268
+ | HostBuilderSpec
269
+ | SettingsProviderSpec
270
+ | InfraProviderSpec
271
+ | RuntimeManifestContributionSpec
272
+ | LifecycleContributionSpec
273
+ | WorkerRoleSpec
274
+ | CliToolSpec
275
+ | PageProfileProviderSpec
276
+ | ResourceContributionSpec
277
+ | ProtocolRegistrationSpec
278
+ | ProtocolExtensionSpec
279
+ )
280
+
281
+
282
+ @dataclass(frozen=True, slots=True)
283
+ class ExtensionRegistry:
284
+ extensions: tuple[FormalExtensionSpec, ...] = ()
285
+
286
+ def by_id(self) -> dict[str, FormalExtensionSpec]:
287
+ return {str(extension.id): extension for extension in self.extensions}
288
+
289
+ def by_kind(self, kind: str) -> tuple[FormalExtensionSpec, ...]:
290
+ expected = str(kind or "").strip()
291
+ if not expected:
292
+ return ()
293
+ return tuple(extension for extension in self.extensions if extension.kind == expected)
294
+
295
+ def validate(self) -> None:
296
+ registered_protocols: dict[str, ProtocolRegistrationSpec] = {}
297
+ protocol_extensions: list[ProtocolExtensionSpec] = []
298
+ ids: list[str] = []
299
+ for extension in self.extensions:
300
+ raw_id = getattr(extension, "id", None)
301
+ if not isinstance(raw_id, str) or not raw_id.strip():
302
+ raise PzError(
303
+ code="PZ_EXTENSION_ID_INVALID",
304
+ message="Extension id must be a non-empty string.",
305
+ stage="extensions",
306
+ owner="core.extensions",
307
+ action="validate",
308
+ )
309
+ raw_owner = getattr(extension, "owner_app", None)
310
+ if not isinstance(raw_owner, str) or not raw_owner.strip():
311
+ raise PzError(
312
+ code="PZ_EXTENSION_OWNER_INVALID",
313
+ message=f"Extension '{raw_id}' must declare owner_app as a non-empty string.",
314
+ stage="extensions",
315
+ owner=str(raw_id),
316
+ action="validate",
317
+ )
318
+ raw_kind = getattr(extension, "kind", None)
319
+ if not isinstance(raw_kind, str) or not raw_kind.strip():
320
+ raise PzError(
321
+ code="PZ_EXTENSION_KIND_INVALID",
322
+ message=f"Extension '{raw_id}' must declare kind as a non-empty string.",
323
+ stage="extensions",
324
+ owner=str(raw_owner),
325
+ action="validate",
326
+ )
327
+ provides = getattr(extension, "provides", ())
328
+ if provides not in (None, ()) and not isinstance(provides, tuple):
329
+ raise PzError(
330
+ code="PZ_EXTENSION_PROVIDES_INVALID",
331
+ message=f"Extension '{raw_id}' must declare provides as a tuple of non-empty strings.",
332
+ stage="extensions",
333
+ owner=str(raw_owner),
334
+ action="validate",
335
+ )
336
+ for item in tuple(provides or ()):
337
+ if not isinstance(item, str) or not item.strip():
338
+ raise PzError(
339
+ code="PZ_EXTENSION_PROVIDES_ITEM_INVALID",
340
+ message=f"Extension '{raw_id}' provides contains an invalid item.",
341
+ stage="extensions",
342
+ owner=str(raw_owner),
343
+ action="validate",
344
+ )
345
+ if isinstance(extension, WorkerRoleSpec):
346
+ if not isinstance(extension.tool_name, str) or not extension.tool_name.strip():
347
+ raise PzError(
348
+ code="PZ_WORKER_ROLE_TOOL_INVALID",
349
+ message=f"Worker role '{raw_id}' must declare tool_name as a non-empty string.",
350
+ stage="extensions",
351
+ owner=str(raw_owner),
352
+ action="validate",
353
+ )
354
+ if extension.restart not in {"always", "never"}:
355
+ raise PzError(
356
+ code="PZ_WORKER_ROLE_RESTART_INVALID",
357
+ message=f"Worker role '{raw_id}' restart must be `always` or `never`.",
358
+ stage="extensions",
359
+ owner=str(raw_owner),
360
+ action="validate",
361
+ )
362
+ if (
363
+ not isinstance(extension.worker_kind, str)
364
+ or extension.worker_kind not in WORKER_ROLE_KINDS
365
+ ):
366
+ raise PzError(
367
+ code="PZ_WORKER_ROLE_KIND_INVALID",
368
+ message=(
369
+ f"Worker role '{raw_id}' worker_kind must be one of: "
370
+ f"{', '.join(sorted(WORKER_ROLE_KINDS))}."
371
+ ),
372
+ stage="extensions",
373
+ owner=str(raw_owner),
374
+ action="validate",
375
+ )
376
+ if (
377
+ extension.auto_start_setting
378
+ and (
379
+ not isinstance(extension.auto_start_setting, str)
380
+ or not extension.auto_start_setting.strip()
381
+ )
382
+ ):
383
+ raise PzError(
384
+ code="PZ_WORKER_ROLE_AUTO_START_SETTING_INVALID",
385
+ message=(
386
+ f"Worker role '{raw_id}' auto_start_setting must be empty or "
387
+ "a non-empty string."
388
+ ),
389
+ stage="extensions",
390
+ owner=str(raw_owner),
391
+ action="validate",
392
+ )
393
+ if (
394
+ extension.replica_count_setting
395
+ and (
396
+ not isinstance(extension.replica_count_setting, str)
397
+ or not extension.replica_count_setting.strip()
398
+ )
399
+ ):
400
+ raise PzError(
401
+ code="PZ_WORKER_ROLE_REPLICA_COUNT_SETTING_INVALID",
402
+ message=(
403
+ f"Worker role '{raw_id}' replica_count_setting must be empty or "
404
+ "a non-empty string."
405
+ ),
406
+ stage="extensions",
407
+ owner=str(raw_owner),
408
+ action="validate",
409
+ )
410
+ if isinstance(extension, ExtensionSpec):
411
+ payload = extension.payload
412
+ if not isinstance(payload, dict):
413
+ raise PzError(
414
+ code="PZ_EXTENSION_PAYLOAD_INVALID",
415
+ message=f"Extension '{raw_id}' payload must be a dict.",
416
+ stage="extensions",
417
+ owner=str(raw_owner),
418
+ action="validate",
419
+ )
420
+ if not _is_declarative_payload_value(payload):
421
+ raise PzError(
422
+ code="PZ_EXTENSION_PAYLOAD_NOT_DECLARATIVE",
423
+ message=(
424
+ f"Extension '{raw_id}' payload must be declarative data only. "
425
+ "Use import strings for runtime objects such as routers, providers, "
426
+ "handlers, or factories."
427
+ ),
428
+ stage="extensions",
429
+ owner=str(raw_owner),
430
+ action="validate",
431
+ )
432
+ if isinstance(extension, RuntimeManifestContributionSpec):
433
+ if str(extension.kind or "").strip() != "runtime.manifest_contribution":
434
+ raise PzError(
435
+ code="PZ_RUNTIME_MANIFEST_CONTRIBUTION_KIND_INVALID",
436
+ message=f"Runtime manifest contribution '{raw_id}' kind must be `runtime.manifest_contribution`.",
437
+ stage="extensions",
438
+ owner=str(raw_owner),
439
+ action="validate",
440
+ )
441
+ provider = extension.provider
442
+ if provider is None:
443
+ raise PzError(
444
+ code="PZ_RUNTIME_MANIFEST_CONTRIBUTION_PROVIDER_INVALID",
445
+ message=f"Runtime manifest contribution '{raw_id}' must declare a provider.",
446
+ stage="extensions",
447
+ owner=str(raw_owner),
448
+ action="validate",
449
+ )
450
+ if isinstance(provider, str) and not provider.strip():
451
+ raise PzError(
452
+ code="PZ_RUNTIME_MANIFEST_CONTRIBUTION_PROVIDER_INVALID",
453
+ message=(
454
+ f"Runtime manifest contribution '{raw_id}' must declare provider as "
455
+ "a non-empty string."
456
+ ),
457
+ stage="extensions",
458
+ owner=str(raw_owner),
459
+ action="validate",
460
+ )
461
+ if isinstance(extension, CliToolSpec):
462
+ if str(extension.kind or "").strip() != "cli.tool":
463
+ raise PzError(
464
+ code="PZ_CLI_TOOL_KIND_INVALID",
465
+ message=f"CLI tool '{raw_id}' kind must be `cli.tool`.",
466
+ stage="extensions",
467
+ owner=str(raw_owner),
468
+ action="validate",
469
+ )
470
+ if not isinstance(extension.command, tuple) or not extension.command:
471
+ raise PzError(
472
+ code="PZ_CLI_TOOL_COMMAND_INVALID",
473
+ message=f"CLI tool '{raw_id}' must declare command as a non-empty tuple.",
474
+ stage="extensions",
475
+ owner=str(raw_owner),
476
+ action="validate",
477
+ )
478
+ for part in extension.command:
479
+ if not isinstance(part, str) or not part.strip():
480
+ raise PzError(
481
+ code="PZ_CLI_TOOL_COMMAND_INVALID",
482
+ message=f"CLI tool '{raw_id}' command contains an invalid segment.",
483
+ stage="extensions",
484
+ owner=str(raw_owner),
485
+ action="validate",
486
+ )
487
+ tool = extension.tool
488
+ if not callable(getattr(tool, "configure", None)) or not callable(getattr(tool, "run", None)):
489
+ raise PzError(
490
+ code="PZ_CLI_TOOL_OBJECT_INVALID",
491
+ message=f"CLI tool '{raw_id}' must expose configure(parser) and run(ctx).",
492
+ stage="extensions",
493
+ owner=str(raw_owner),
494
+ action="validate",
495
+ )
496
+ if isinstance(extension, PageProfileProviderSpec):
497
+ provider = extension.provider
498
+ if provider is None:
499
+ raise PzError(
500
+ code="PZ_PAGE_PROFILE_PROVIDER_INVALID",
501
+ message=f"Page-profile provider '{raw_id}' must declare a provider.",
502
+ stage="extensions",
503
+ owner=str(raw_owner),
504
+ action="validate",
505
+ )
506
+ if isinstance(provider, str) and not provider.strip():
507
+ raise PzError(
508
+ code="PZ_PAGE_PROFILE_PROVIDER_INVALID",
509
+ message=f"Page-profile provider '{raw_id}' must declare provider as a non-empty string.",
510
+ stage="extensions",
511
+ owner=str(raw_owner),
512
+ action="validate",
513
+ )
514
+ if isinstance(extension, ResourceContributionSpec):
515
+ if str(extension.kind or "").strip() != "resource.contribution":
516
+ raise PzError(
517
+ code="PZ_RESOURCE_CONTRIBUTION_KIND_INVALID",
518
+ message=f"Resource contribution '{raw_id}' kind must be `resource.contribution`.",
519
+ stage="extensions",
520
+ owner=str(raw_owner),
521
+ action="validate",
522
+ )
523
+ for field_name in ("templates_dir", "static_dir", "locales_dir", "migrations_dir"):
524
+ value = getattr(extension, field_name)
525
+ if not isinstance(value, str) or not value.strip():
526
+ raise PzError(
527
+ code="PZ_RESOURCE_CONTRIBUTION_FIELD_INVALID",
528
+ message=(
529
+ f"Resource contribution '{raw_id}' field '{field_name}' "
530
+ "must be a non-empty string."
531
+ ),
532
+ stage="extensions",
533
+ owner=str(raw_owner),
534
+ action="validate",
535
+ )
536
+ for attr_name in (
537
+ "locale_bundles",
538
+ "locale_dependencies",
539
+ "locale_consumer_bindings",
540
+ "locale_profiles",
541
+ "locale_overrides",
542
+ ):
543
+ value = getattr(extension, attr_name)
544
+ if value not in (None, ()) and not isinstance(value, tuple):
545
+ raise PzError(
546
+ code="PZ_RESOURCE_CONTRIBUTION_FIELD_INVALID",
547
+ message=(
548
+ f"Resource contribution '{raw_id}' field '{attr_name}' must be a tuple."
549
+ ),
550
+ stage="extensions",
551
+ owner=str(raw_owner),
552
+ action="validate",
553
+ )
554
+ if extension.locale_allowlist not in (None, ()) and not isinstance(extension.locale_allowlist, tuple):
555
+ raise PzError(
556
+ code="PZ_RESOURCE_CONTRIBUTION_FIELD_INVALID",
557
+ message=(
558
+ f"Resource contribution '{raw_id}' field 'locale_allowlist' must be a tuple."
559
+ ),
560
+ stage="extensions",
561
+ owner=str(raw_owner),
562
+ action="validate",
563
+ )
564
+ for item in tuple(extension.locale_allowlist or ()):
565
+ if not isinstance(item, str) or not item.strip():
566
+ raise PzError(
567
+ code="PZ_RESOURCE_CONTRIBUTION_FIELD_INVALID",
568
+ message=(
569
+ f"Resource contribution '{raw_id}' field 'locale_allowlist' contains an invalid item."
570
+ ),
571
+ stage="extensions",
572
+ owner=str(raw_owner),
573
+ action="validate",
574
+ )
575
+ if isinstance(extension, ProtocolRegistrationSpec):
576
+ if str(extension.kind or "").strip() != "protocol.registration":
577
+ raise PzError(
578
+ code="PZ_PROTOCOL_REGISTRATION_KIND_INVALID",
579
+ message=f"Protocol registration '{raw_id}' kind must be `protocol.registration`.",
580
+ stage="extensions",
581
+ owner=str(raw_owner),
582
+ action="validate",
583
+ )
584
+ protocol_kind = str(extension.protocol_kind or "").strip()
585
+ if not protocol_kind:
586
+ raise PzError(
587
+ code="PZ_PROTOCOL_KIND_INVALID",
588
+ message=f"Protocol registration '{raw_id}' must declare protocol_kind.",
589
+ stage="extensions",
590
+ owner=str(raw_owner),
591
+ action="validate",
592
+ )
593
+ if protocol_kind in registered_protocols:
594
+ raise PzError(
595
+ code="PZ_PROTOCOL_KIND_DUPLICATE",
596
+ message=f"Protocol kind '{protocol_kind}' is registered more than once.",
597
+ stage="extensions",
598
+ owner=str(raw_owner),
599
+ action="validate",
600
+ )
601
+ if not isinstance(extension.spec_name, str) or not extension.spec_name.strip():
602
+ raise PzError(
603
+ code="PZ_PROTOCOL_SPEC_NAME_INVALID",
604
+ message=f"Protocol registration '{raw_id}' must declare spec_name.",
605
+ stage="extensions",
606
+ owner=str(raw_owner),
607
+ action="validate",
608
+ )
609
+ if not isinstance(extension.runtime_owner, str) or not extension.runtime_owner.strip():
610
+ raise PzError(
611
+ code="PZ_PROTOCOL_RUNTIME_OWNER_INVALID",
612
+ message=f"Protocol registration '{raw_id}' must declare runtime_owner.",
613
+ stage="extensions",
614
+ owner=str(raw_owner),
615
+ action="validate",
616
+ )
617
+ for attr_name, code in (
618
+ ("required_fields", "PZ_PROTOCOL_REQUIRED_FIELDS_INVALID"),
619
+ ("string_fields", "PZ_PROTOCOL_STRING_FIELDS_INVALID"),
620
+ ("import_fields", "PZ_PROTOCOL_IMPORT_FIELDS_INVALID"),
621
+ ("callable_fields", "PZ_PROTOCOL_CALLABLE_FIELDS_INVALID"),
622
+ ):
623
+ value = getattr(extension, attr_name, ())
624
+ if value not in (None, ()) and not isinstance(value, tuple):
625
+ raise PzError(
626
+ code=code,
627
+ message=f"Protocol registration '{raw_id}' must declare {attr_name} as a tuple.",
628
+ stage="extensions",
629
+ owner=str(raw_owner),
630
+ action="validate",
631
+ )
632
+ for item in tuple(value or ()):
633
+ if not isinstance(item, str) or not item.strip():
634
+ raise PzError(
635
+ code=code,
636
+ message=f"Protocol registration '{raw_id}' contains an invalid {attr_name} item.",
637
+ stage="extensions",
638
+ owner=str(raw_owner),
639
+ action="validate",
640
+ )
641
+ validator = extension.validator
642
+ if validator is not None and not (
643
+ callable(validator) or (isinstance(validator, str) and validator.strip())
644
+ ):
645
+ raise PzError(
646
+ code="PZ_PROTOCOL_VALIDATOR_INVALID",
647
+ message=f"Protocol registration '{raw_id}' validator must be callable or a non-empty string.",
648
+ stage="extensions",
649
+ owner=str(raw_owner),
650
+ action="validate",
651
+ )
652
+ registered_protocols[protocol_kind] = extension
653
+ if isinstance(extension, ProtocolExtensionSpec):
654
+ protocol_extensions.append(extension)
655
+ ids.append(raw_id)
656
+ if len(ids) != len(set(ids)):
657
+ raise PzError(
658
+ code="PZ_EXTENSION_DUPLICATE_ID",
659
+ message="ExtensionRegistry contains duplicate extension ids.",
660
+ stage="extensions",
661
+ owner="core.extensions",
662
+ action="validate",
663
+ )
664
+
665
+ for extension in self.extensions:
666
+ if isinstance(extension, ExtensionSpec) and extension.kind in registered_protocols:
667
+ raise PzError(
668
+ code="PZ_PROTOCOL_EXTENSION_SPEC_REQUIRED",
669
+ message=(
670
+ f"Extension '{extension.id}' kind '{extension.kind}' is a registered protocol and "
671
+ "must use ProtocolExtensionSpec."
672
+ ),
673
+ stage="extensions",
674
+ owner=extension.owner_app,
675
+ action="validate",
676
+ )
677
+
678
+ for extension in protocol_extensions:
679
+ raw_id = str(extension.id or "").strip()
680
+ raw_owner = str(extension.owner_app or "").strip()
681
+ protocol = registered_protocols.get(str(extension.kind or "").strip())
682
+ if protocol is None:
683
+ if extension.optional:
684
+ continue
685
+ raise PzError(
686
+ code="PZ_PROTOCOL_EXTENSION_UNREGISTERED",
687
+ message=f"Protocol extension '{raw_id}' declares unknown kind '{extension.kind}'.",
688
+ stage="extensions",
689
+ owner=str(raw_owner),
690
+ action="validate",
691
+ )
692
+ if not isinstance(extension.fields, dict):
693
+ raise PzError(
694
+ code="PZ_PROTOCOL_FIELDS_INVALID",
695
+ message=f"Protocol extension '{raw_id}' must declare fields as a dict.",
696
+ stage="extensions",
697
+ owner=str(raw_owner),
698
+ action="validate",
699
+ )
700
+ for field_name in protocol.required_fields:
701
+ if field_name not in extension.fields:
702
+ raise PzError(
703
+ code="PZ_PROTOCOL_REQUIRED_FIELD_MISSING",
704
+ message=f"Protocol extension '{raw_id}' is missing required field '{field_name}'.",
705
+ stage="extensions",
706
+ owner=str(raw_owner),
707
+ action="validate",
708
+ )
709
+
710
+ for field_name in protocol.string_fields:
711
+ value = extension.fields.get(field_name)
712
+ if not isinstance(value, str) or not value.strip():
713
+ raise PzError(
714
+ code="PZ_PROTOCOL_STRING_FIELD_INVALID",
715
+ message=f"Protocol extension '{raw_id}' field '{field_name}' must be a non-empty string.",
716
+ stage="extensions",
717
+ owner=str(raw_owner),
718
+ action="validate",
719
+ )
720
+ validator = protocol.validator
721
+ if validator is not None:
722
+ validator_callable = _instantiate_extension_target(validator)
723
+ if not callable(validator_callable):
724
+ raise PzError(
725
+ code="PZ_PROTOCOL_VALIDATOR_INVALID",
726
+ message=f"Protocol registration '{protocol.id}' validator must resolve to callable.",
727
+ stage="extensions",
728
+ owner=str(raw_owner),
729
+ action="validate",
730
+ )
731
+ validator_callable(extension)
732
+ for field_name in protocol.import_fields:
733
+ value = extension.fields.get(field_name)
734
+ if not isinstance(value, str) or not value.strip():
735
+ raise PzError(
736
+ code="PZ_PROTOCOL_IMPORT_FIELD_INVALID",
737
+ message=f"Protocol extension '{raw_id}' field '{field_name}' must be a non-empty import string.",
738
+ stage="extensions",
739
+ owner=str(raw_owner),
740
+ action="validate",
741
+ )
742
+ try:
743
+ _instantiate_extension_target(value)
744
+ except Exception as exc:
745
+ raise PzError(
746
+ code="PZ_PROTOCOL_IMPORT_RESOLVE_FAILED",
747
+ message=(
748
+ f"Protocol extension '{raw_id}' field '{field_name}' failed to load during "
749
+ f"validation: {exc}"
750
+ ),
751
+ stage="extensions",
752
+ owner=str(raw_owner),
753
+ action="validate",
754
+ ) from exc
755
+ for field_name in protocol.callable_fields:
756
+ value = extension.fields.get(field_name)
757
+ if value is None:
758
+ raise PzError(
759
+ code="PZ_PROTOCOL_CALLABLE_FIELD_INVALID",
760
+ message=f"Protocol extension '{raw_id}' field '{field_name}' must be callable.",
761
+ stage="extensions",
762
+ owner=str(raw_owner),
763
+ action="validate",
764
+ )
765
+ if not callable(_instantiate_extension_target(value)):
766
+ raise PzError(
767
+ code="PZ_PROTOCOL_CALLABLE_FIELD_INVALID",
768
+ message=f"Protocol extension '{raw_id}' field '{field_name}' must resolve to callable.",
769
+ stage="extensions",
770
+ owner=str(raw_owner),
771
+ action="validate",
772
+ )
773
+
774
+ installed = self.by_id()
775
+ for extension in self.extensions:
776
+ conflicts = tuple(getattr(extension, "conflicts", ()) or ())
777
+ owner_app = str(getattr(extension, "owner_app", "") or "")
778
+ extension_id = str(getattr(extension, "id", "") or "")
779
+ for conflict in conflicts:
780
+ if conflict in installed:
781
+ raise PzError(
782
+ code="PZ_EXTENSION_CONFLICT",
783
+ message=(
784
+ f"Extension '{extension_id}' conflicts with installed "
785
+ f"extension '{conflict}'."
786
+ ),
787
+ stage="extensions",
788
+ owner=owner_app or extension_id,
789
+ action="validate",
790
+ )