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,680 @@
1
+ # 公开免费接口防滥用与人机校验
2
+
3
+ ## 背景
4
+
5
+ 很多产品都会提供匿名免费试用、公开诊断、公开生成、公开提交等入口。这类入口有两个共同特点:
6
+
7
+ - 用户不一定登录,不能直接依赖 auth owner 做身份拦截。
8
+ - 后端动作有真实成本,可能调用 LLM、搜索、邮件、短信、爬取、导出或第三方 API。
9
+
10
+ 因此,“让用户只能在现有网页上交互”不能理解为浏览器来源检查。`Origin`、`Referer`、CORS、User-Agent 都可以被脚本绕过或缺失,只能作为辅助信号。正式方案必须由源站验证一组服务端可判定的凭据:
11
+
12
+ - 同源 unsafe request 约束。
13
+ - SSR 页面签发的短时 action token。
14
+ - 人机验证 provider 的服务端校验。
15
+ - action 级限流、去重、成本预算与可观测审计。
16
+
17
+ 该能力属于 `pz_foundation` 的通用 HTTP / middleware / security 能力,不属于某个业务 app 私有实现。
18
+
19
+ ## 定位
20
+
21
+ 建议命名为 `Public Action Gate`。
22
+
23
+ `Public Action Gate` 负责保护匿名或半匿名公开动作,例如:
24
+
25
+ - 免费诊断提交。
26
+ - 联系表单。
27
+ - 邮件订阅。
28
+ - 一次性 AI 生成 / 分析。
29
+ - 未登录 checkout 前置动作。
30
+
31
+ 它不负责:
32
+
33
+ - 登录态认证、用户 session、角色权限;这些仍由 auth owner 负责。
34
+ - 订阅、付费、业务额度和套餐权益;这些由业务 owner 负责。
35
+ - 判断业务请求是否“应该成功”;foundation 只输出通用 gate verdict。
36
+ - 替代 Cloudflare WAF、Bot Management 或平台侧 rate limit;边缘层只能作为第一道防线,源站仍必须验证。
37
+
38
+ ## 正式目标
39
+
40
+ 1. 公开高成本接口默认不能被裸 API 调用直接消费。
41
+ 2. 业务页面仍是交互入口;页面首屏由业务 app SSR,foundation 只提供可复用 token、校验和中间件能力。
42
+ 3. 所有高成本公开动作都必须有稳定 `action_id`,进入可审计 manifest。
43
+ 4. 人机验证必须服务端校验,不能只在页面上渲染 widget。
44
+ 5. 限流和配额以 action 为粒度,不把所有公开 API 混成一类。
45
+ 6. secret 只进入 `configs/default.env` / 项目 env layer,不写入 JSONC、模板、页面或日志。
46
+
47
+ ## 现有分散点
48
+
49
+ 当前仓库里已经有一批相近能力,但职责分散在不同子包和中间件里:
50
+
51
+ - `pz_foundation.middlewares.rate_limited.KeyLimiter`
52
+ - 已经提供 Redis + 进程内 fallback 的 key window 限制能力。
53
+ - 目前只是底层计数器,不知道 action、客户端身份、payload hash、human verification 或 fail-open / fail-closed policy。
54
+ - `pz_foundation.middlewares.security_csrf.SecurityCSRFMiddleware`
55
+ - 已经提供 double-submit CSRF 校验。
56
+ - 目前只按 path prefix 工作,不理解 public action manifest。
57
+ - `pz_api_access.middlewares.rate_limited`
58
+ - 已有 path / method / pre_auth / post_auth 规则、IP burst / daily limit、规则快照缓存。
59
+ - 目前 IP 解析、规则匹配、KeyLimiter 构造和响应语义都在 middleware 内部。
60
+ - `pz_api_access.middlewares.risk_control`
61
+ - 已有 `ip`、`email`、`ip_email`、`token` 等风险 key 维度。
62
+ - 目前 body 解析、key 归一化、规则命中和 limiter 编排都在 middleware 内部。
63
+ - `pz_api_access.deps.require_api_token`
64
+ - 已有 PAT 级本地限流和 Redis 限流。
65
+ - 这是外部 API token 访问控制,不应被 public action gate 接管,但可以复用统一 limiter / client identity 对象。
66
+ - `pz_page_center.review_state`
67
+ - 有 review quota、Redis reserve、local fallback 和 override。
68
+ - 这是 Page Center 自己的治理额度,不属于通用公开动作防滥用,但其中的 reserve store 语义可以作为 foundation quota counter 的参考。
69
+ - `pz_site_media.services.enforce`
70
+ - 有媒体存储业务 quota。
71
+ - 这是业务/套餐额度,不应迁入 foundation;foundation 只能提供请求保护 verdict,media 继续拥有存储额度真相。
72
+ - `showinai/docs/公开工具设计.md`
73
+ - 已经声明 IP + payload + domain + email 维度限流。
74
+ - 这类匿名公开高成本动作应改为贡献 public action declaration,由 foundation 统一执行通用 gate。
75
+
76
+ 正式收敛方向:
77
+
78
+ - foundation 拥有通用 HTTP request protection primitives。
79
+ - `pz_api_access` 继续拥有外部 API token / PAT 生命周期和外部 API 权限。
80
+ - `pz_auth` 继续拥有登录态认证、session control 和 route policy。
81
+ - 业务子包继续拥有套餐、积分、存储、报告、provider 成本等业务额度。
82
+ - 业务子包不再复制 IP 解析、Turnstile 校验、action token、通用 nonce 和通用 rate limit 编排。
83
+
84
+ ## 核心结论
85
+
86
+ 公开免费接口的最小防护链路为:
87
+
88
+ ```text
89
+ GET public page
90
+ -> SSR 输出表单、csrf cookie、public_action_token、human verification site key
91
+
92
+ POST public action API
93
+ -> route owner / canonical middleware
94
+ -> CORS / same-origin / CSRF check
95
+ -> PublicActionGate 校验 action token
96
+ -> PublicActionGate 服务端校验 human verification token
97
+ -> PublicActionGate 检查 action rate limit / budget / replay
98
+ -> business handler
99
+ -> business service
100
+ ```
101
+
102
+ 其中 `public_action_token` 和人机验证 token 是两类不同凭据:
103
+
104
+ - `public_action_token` 证明请求来自 foundation 最近签发过的页面交互上下文。
105
+ - 人机验证 token 证明本次提交通过了外部 provider 的挑战校验。
106
+
107
+ 两者都必须通过服务端验证。只做其中一个都不完整。
108
+
109
+ ## 面向对象统一模型
110
+
111
+ Public Action Gate 不应实现成一个巨大 middleware,也不应继续把规则解析、IP 解析、token 签发、limiter、Turnstile adapter、日志和业务判断散落到 handler 里。
112
+
113
+ 按 `pz` 面向对象规范,建议拆成以下对象:
114
+
115
+ ```text
116
+ PublicActionProtectionSpec # 声明期:业务 app 贡献 action 保护声明
117
+ PublicActionProtectionManifest # 编译期:RuntimePlan 内不可变保护清单
118
+ PublicActionGateSettingsSnapshot # 编译期:foundation settings 投影
119
+
120
+ PublicActionGateRuntime # runtime owner / 组合根
121
+ -> PublicActionGateFacade # 对业务页面和中间件暴露的窄公开面
122
+ -> ClientIdentityResolver # 解析可信 IP、国家、session、用户、header facts
123
+ -> ActionTokenSigner # 内存运行时 signer,负责签发/验签 action token
124
+ -> ActionNonceStore # 一次性 nonce consume / replay 防护
125
+ -> HumanVerificationProvider # Turnstile 等 provider 协议
126
+ -> RateLimitEvaluator # action 级限流编排
127
+ -> RateLimitKeyBuilder # ip / session / payload / email / domain key 归一化
128
+ -> CounterStore # Redis / local fallback / future durable store
129
+ -> BudgetEvaluator # 通用项目级 provider budget,可选
130
+ -> PublicActionAuditSink # 安全日志与指标
131
+ ```
132
+
133
+ ### 声明期对象
134
+
135
+ `PublicActionProtectionSpec` 由业务 app 贡献,描述“哪个业务动作需要哪些 gate”,不直接保存运行状态:
136
+
137
+ ```text
138
+ action_id
139
+ owner_app
140
+ route_name / route_namespace
141
+ methods
142
+ required_gates
143
+ payload_hash_fields
144
+ rate_limit_policy_ids
145
+ human_verification_action
146
+ fail_closed
147
+ ```
148
+
149
+ 要求:
150
+
151
+ - `action_id` 稳定、全局唯一,例如 `showinai.public_geo_run`。
152
+ - path projection 从 route manifest 解析,不长期以 settings path 字符串作为唯一真相。
153
+ - payload hash 字段必须由业务 owner 显式声明;foundation 不猜字段。
154
+
155
+ ### 编译对象
156
+
157
+ `PublicActionProtectionCompiler` 负责把业务贡献、route manifest 和 foundation settings 编译为 `PublicActionProtectionManifest`。
158
+
159
+ 要求:
160
+
161
+ - 在 RuntimePlan 编译期完成冲突检查。
162
+ - 检查 action 是否指向存在的 route。
163
+ - 检查 high-cost action 是否声明了 `public_action_token`、`human_verification` 和至少一个限流策略。
164
+ - 检查 secret 缺失时 product 环境 fail-fast;develop / test 可使用显式 disabled 或 test provider。
165
+ - 输出不可变 manifest;Web 执行阶段只消费 manifest。
166
+
167
+ ### Runtime Owner
168
+
169
+ `PublicActionGateRuntime` 是唯一 runtime owner。
170
+
171
+ 职责:
172
+
173
+ - 从 `PublicActionProtectionManifest` 与 `PublicActionGateSettingsSnapshot` 构造对象图。
174
+ - 在内存中持有 `ActionTokenSigner`、provider adapter、counter store、nonce store 和 audit sink。
175
+ - 对外暴露 `PublicActionGateFacade`。
176
+ - 统一生命周期,后续如果 provider client 或 store 需要关闭,由 runtime owner 对称释放。
177
+
178
+ 不允许:
179
+
180
+ - handler 内临时创建 Turnstile client。
181
+ - handler 内临时读取 env secret。
182
+ - 业务 app 自己 new `KeyLimiter` 保护 public action。
183
+ - action token signer 作为模块级全局函数散落在业务模块。
184
+
185
+ ### Runtime Signer
186
+
187
+ 把 token 生成函数放在服务端内存中是合理的,但正式对象应是 `ActionTokenSigner`,而不是匿名函数。
188
+
189
+ 语义:
190
+
191
+ - 启动或 runtime activate 时,由 foundation 根据 settings/env secret 构造 signer。
192
+ - 请求期通过 `PublicActionGateFacade.issue_action_token(...)` 使用 signer。
193
+ - signer 持有密钥派生结果、算法版本、默认 TTL 和 clock 依赖。
194
+ - signer 不读取 settings、不访问 request、不做业务判断。
195
+
196
+ 收益:
197
+
198
+ - 请求热路径不重复解析 settings/env。
199
+ - 业务 app 不接触签名 secret。
200
+ - token 算法和版本可替换、可测试。
201
+ - 符合 RuntimePlan “编译先于运行”的对象图。
202
+
203
+ 边界:
204
+
205
+ - signer 只能在服务端内存中。
206
+ - 浏览器端不能生成可信 action token。
207
+ - signer 缓存不是安全边界;安全边界仍是服务端 secret、短 TTL、nonce 单次消费和 gate 校验。
208
+
209
+ ### Client Identity Resolver
210
+
211
+ IP 限制必须统一先解决“可信客户端身份”问题。
212
+
213
+ `ClientIdentityResolver` 负责输出结构化 `ClientIdentity`:
214
+
215
+ ```text
216
+ client_ip
217
+ client_ip_source
218
+ country
219
+ ray_id
220
+ user_agent_hash
221
+ origin
222
+ referer
223
+ anonymous_session_hash
224
+ authenticated_user_id
225
+ is_trusted_proxy_chain
226
+ ```
227
+
228
+ 规则:
229
+
230
+ - 只在请求来自可信代理时消费 `CF-Connecting-IP`、`X-Forwarded-For`、`X-Real-IP`。
231
+ - Cloudflare 部署下优先读取 `CF-Connecting-IP`,并记录 `CF-Ray` 用于审计。
232
+ - 未确认可信代理时只使用 ASGI client host。
233
+ - IP 原文默认不进入业务日志;审计日志可记录 hash 和来源。
234
+ - 业务 app 不再自己解析 `x-forwarded-for`。
235
+
236
+ ### Rate Limit Evaluator
237
+
238
+ `RateLimitEvaluator` 统一消费 action policy 和 `ClientIdentity`,输出 `RateLimitVerdict`。
239
+
240
+ 支持维度:
241
+
242
+ - `ip`
243
+ - `anonymous_session`
244
+ - `authenticated_user`
245
+ - `payload_hash`
246
+ - `email_hash`
247
+ - `domain_hash`
248
+ - `ip_payload_hash`
249
+ - `ip_email_hash`
250
+ - `country`
251
+ - `global_action`
252
+
253
+ 要求:
254
+
255
+ - key 构造由 `RateLimitKeyBuilder` 负责,字段归一化和 hash 策略可测试。
256
+ - 计数器由 `CounterStore` 负责,首版可复用现有 `KeyLimiter`。
257
+ - Redis 不可用时如何处理由 action policy 决定:
258
+ - 高成本 public action 默认 fail-closed 或进入极低本地 fallback limit。
259
+ - 低成本 action 可以显式 fail-open。
260
+ - verdict 必须包含 `allowed`、`retry_after_seconds`、`policy_id`、`dimension`、`store_status`。
261
+
262
+ ### Facade
263
+
264
+ `PublicActionGateFacade` 是业务 app 唯一稳定消费面:
265
+
266
+ ```text
267
+ issue_action_token(action_id, request_context) -> IssuedPublicActionToken
268
+ verify_request(action_id, request) -> PublicActionGateVerdict
269
+ build_template_context(action_id, request_context) -> PublicActionTemplateContext
270
+ ```
271
+
272
+ 业务页面使用 `build_template_context(...)` 输出 site key、action token 和字段名。
273
+
274
+ 业务 API handler 可以选择两种集成方式:
275
+
276
+ - 通过 foundation middleware 在进入 handler 前 fail-fast。
277
+ - 通过 dependency/facade 显式调用 `verify_request(...)` 并接收 verdict。
278
+
279
+ 同一 action 只能选一种正式执行路径,避免 middleware 和 handler 双重扣次数。
280
+
281
+ ## Action Token
282
+
283
+ ### 语义
284
+
285
+ `public_action_token` 是 foundation 签发的短时、单用途、可绑定上下文的 action 凭据。
286
+
287
+ 建议字段:
288
+
289
+ - `version`
290
+ - `action_id`
291
+ - `owner_app`
292
+ - `method`
293
+ - `path`
294
+ - `issued_at`
295
+ - `expires_at`
296
+ - `nonce`
297
+ - `anonymous_session_id` 或登录态 `user_id` 的不可逆摘要
298
+ - `payload_policy_id`
299
+ - `signature`
300
+
301
+ 要求:
302
+
303
+ - token 由 foundation 使用 HMAC 或同等强度签名,不允许业务 app 自己拼接。
304
+ - 默认有效期不超过 10 分钟;高成本动作建议 2-5 分钟。
305
+ - nonce 必须一次性消费;重复提交返回 `action_token_replayed`。
306
+ - token 不包含 email、关键词、domain、prompt、URL 等业务明文。
307
+ - token 可以绑定 `method + path + action_id`,避免被挪用到其它接口。
308
+ - token 可以绑定匿名 session cookie,但不能把 cookie 当成身份认证。
309
+
310
+ ### 签发入口
311
+
312
+ 业务页面在 SSR 时通过 foundation facade 请求签发:
313
+
314
+ ```text
315
+ foundation.public_action_gate.issue_token(action_id, request_context)
316
+ ```
317
+
318
+ 模板只拿到:
319
+
320
+ - public action token
321
+ - csrf token / cookie
322
+ - human verification site key
323
+ - action id
324
+
325
+ 业务 app 不接触签名 secret。
326
+
327
+ ## 人机验证 Provider
328
+
329
+ ### Provider 抽象
330
+
331
+ foundation 应定义 provider-neutral contract:
332
+
333
+ ```text
334
+ HumanVerificationProvider.verify(
335
+ provider_token,
336
+ remote_ip,
337
+ expected_action,
338
+ expected_hostname,
339
+ idempotency_key,
340
+ ) -> HumanVerificationVerdict
341
+ ```
342
+
343
+ 首个正式实现建议为 `cloudflare_turnstile`。
344
+
345
+ 正式 verdict 至少包含:
346
+
347
+ - `success`
348
+ - `provider`
349
+ - `hostname`
350
+ - `action`
351
+ - `challenge_ts`
352
+ - `error_codes`
353
+ - `score` 或 provider 扩展字段
354
+ - `raw_metadata_ref`
355
+
356
+ `raw_metadata_ref` 指向内部日志或原始响应引用,不进入公开响应。
357
+
358
+ ### Cloudflare Turnstile
359
+
360
+ Cloudflare Turnstile 的正式集成要求:
361
+
362
+ - 页面只渲染 site key;secret key 只能在服务端使用。
363
+ - 提交接口接收 `cf-turnstile-response` 或 foundation 统一字段名。
364
+ - 源站必须调用 Cloudflare Siteverify API 完成校验。
365
+ - token 过期或重复使用时必须拒绝本次业务动作。
366
+ - 校验时应传 `remoteip`;可传 `idempotency_key` 支持安全重试。
367
+ - 必须校验返回的 `hostname` 与当前公开域名匹配。
368
+ - 如果启用了 action 绑定,必须校验返回的 `action` 与 `action_id` 或 provider action 映射一致。
369
+
370
+ Cloudflare 官方文档说明 Turnstile token 需要服务端验证,并且 token 是短时、单次使用的。当前官方 Siteverify 端点为:
371
+
372
+ ```text
373
+ POST https://challenges.cloudflare.com/turnstile/v0/siteverify
374
+ ```
375
+
376
+ 参考:
377
+
378
+ - https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
379
+
380
+ ### 失败策略
381
+
382
+ 高成本公开动作必须 fail-closed:
383
+
384
+ - provider token 缺失:`human_verification_required`
385
+ - provider token 无效:`human_verification_failed`
386
+ - provider 超时:`human_verification_unavailable`
387
+ - hostname / action 不匹配:`human_verification_mismatch`
388
+
389
+ 低成本动作可以由 action policy 显式声明降级,但默认不降级。
390
+
391
+ ## 限流与预算
392
+
393
+ Public Action Gate 必须支持 action 级 rate limit。维度建议:
394
+
395
+ - `action_id`
396
+ - client IP 或可信代理解析出的真实 IP
397
+ - anonymous session id
398
+ - authenticated user id
399
+ - payload hash
400
+ - email hash
401
+ - domain hash
402
+ - country / region
403
+
404
+ 典型公开免费诊断动作:
405
+
406
+ ```text
407
+ ip + action_id: 10 / day
408
+ anonymous_session + action_id: 5 / day
409
+ payload_hash + action_id: 3 / hour
410
+ domain_hash + action_id: 20 / day
411
+ global action concurrency: 3
412
+ global provider budget: project-defined
413
+ ```
414
+
415
+ 要求:
416
+
417
+ - payload hash 只基于业务 owner 明确声明的字段生成,不由 foundation 猜测。
418
+ - hash 使用归一化后的值;不在日志里输出原始 email、domain、keyword。
419
+ - Redis / cache 可用时使用共享计数器。
420
+ - Redis 不可用时可以使用进程内 fallback 作为短期保护,但高成本 action 不得因为共享限流不可用而无限放行。
421
+ - 超限响应必须包含稳定错误码和可选 `retry_after_seconds`。
422
+
423
+ ## Origin、CORS 与 CSRF
424
+
425
+ 同源检查不是完整安全方案,但仍必须作为基础门槛:
426
+
427
+ - unsafe methods 默认要求 `Origin` 或可信 `Referer` 与当前 canonical host 匹配。
428
+ - 受 Public Action Gate 保护的接口不得使用 `Access-Control-Allow-Origin: *` 搭配 credentials。
429
+ - 跨域公开 API 必须显式声明为跨域产品能力,并使用独立 action policy。
430
+ - CSRF double-submit cookie 继续由 foundation `security_csrf` 负责;Public Action Gate 不再复制第二套 CSRF。
431
+ - 缺失 `Origin` 的浏览器兼容场景可以回退到 `Referer`,但命中高风险 action 时应要求 action token 与人机验证同时通过。
432
+
433
+ ## 与 Route Manifest 的关系
434
+
435
+ 业务 app 仍通过 `<pkg>.urls` 声明 URL。Public Action Gate 不成为第二套路由真相源。
436
+
437
+ 建议新增一个正式 manifest:
438
+
439
+ ```text
440
+ PublicActionProtectionManifest
441
+ - action_id
442
+ - owner_app
443
+ - methods
444
+ - route namespace / route name
445
+ - path projection
446
+ - required gates
447
+ - rate limit policy ids
448
+ - payload hash policy
449
+ ```
450
+
451
+ 贡献方式:
452
+
453
+ - 业务 app 在 route hints 或正式 extension 中声明 action protection。
454
+ - foundation 在 RuntimePlan 编译阶段把这些声明编译为 manifest。
455
+ - Web 执行阶段只消费 manifest,不运行时扫描业务路由。
456
+
457
+ 业务 app 不应在 handler 里临时写:
458
+
459
+ - 自己调用 Turnstile Siteverify。
460
+ - 自己解析 Cloudflare header。
461
+ - 自己维护匿名 action nonce。
462
+ - 自己维护通用 IP 限流。
463
+
464
+ 如果业务有额外额度或付费规则,应在业务 service 内基于 foundation verdict 之后继续判断。
465
+
466
+ ## Settings 口径
467
+
468
+ 通用配置 owner 为 `pz_foundation`。
469
+
470
+ 建议 settings namespace:
471
+
472
+ ```jsonc
473
+ {
474
+ "public_action_gate": {
475
+ "enabled": true,
476
+ "token_ttl_seconds": 300,
477
+ "nonce_store": "cache",
478
+ "fail_closed_on_nonce_store_unavailable": true,
479
+ "trusted_proxy_headers": [
480
+ "cf-connecting-ip",
481
+ "x-forwarded-for"
482
+ ],
483
+ "human_verification": {
484
+ "provider": "cloudflare_turnstile",
485
+ "site_key": "",
486
+ "secret_key": "",
487
+ "siteverify_url": "https://challenges.cloudflare.com/turnstile/v0/siteverify",
488
+ "timeout_seconds": 3.0,
489
+ "expected_hostnames": []
490
+ },
491
+ "actions": {}
492
+ }
493
+ }
494
+ ```
495
+
496
+ 密钥作者面:
497
+
498
+ ```env
499
+ pz_foundation__public_action_gate__human_verification__secret_key=
500
+ ```
501
+
502
+ `site_key` 可以是非敏感配置;`secret_key` 只能进入 env layer。
503
+
504
+ 业务 action 的阈值可以通过 action id 覆盖:
505
+
506
+ ```jsonc
507
+ {
508
+ "public_action_gate": {
509
+ "actions": {
510
+ "showinai.public_geo_run": {
511
+ "enabled": true,
512
+ "require_action_token": true,
513
+ "require_human_verification": true,
514
+ "rate_limits": [
515
+ {
516
+ "dimension": "ip",
517
+ "limit": 10,
518
+ "window_seconds": 86400
519
+ },
520
+ {
521
+ "dimension": "payload_hash",
522
+ "limit": 3,
523
+ "window_seconds": 3600
524
+ }
525
+ ]
526
+ }
527
+ }
528
+ }
529
+ }
530
+ ```
531
+
532
+ 注意:path 仍来自 route manifest 或业务 action declaration,不应长期只靠 settings 里的 path 字符串定义保护范围。
533
+
534
+ ## 响应与错误码
535
+
536
+ Public Action Gate 应输出稳定错误码,业务页面据此刷新 widget、提示重试或引导登录。
537
+
538
+ 推荐错误码:
539
+
540
+ - `public_action_required`
541
+ - `action_token_missing`
542
+ - `action_token_invalid`
543
+ - `action_token_expired`
544
+ - `action_token_replayed`
545
+ - `csrf_failed`
546
+ - `origin_rejected`
547
+ - `human_verification_required`
548
+ - `human_verification_failed`
549
+ - `human_verification_unavailable`
550
+ - `human_verification_mismatch`
551
+ - `rate_limited`
552
+ - `public_action_budget_exhausted`
553
+
554
+ HTTP 状态建议:
555
+
556
+ - `400`:字段缺失或格式错误。
557
+ - `403`:token、CSRF、origin、人机验证失败。
558
+ - `409`:token replay 或 idempotency 冲突。
559
+ - `429`:限流或预算耗尽。
560
+ - `503`:verification provider 或 nonce store 不可用且 action policy 要求 fail-closed。
561
+
562
+ ## 日志与隐私
563
+
564
+ 日志必须包含足够审计信息,但不能泄漏用户输入和 secret。
565
+
566
+ 允许记录:
567
+
568
+ - `action_id`
569
+ - `owner_app`
570
+ - `verdict`
571
+ - `error_code`
572
+ - `limit_policy_id`
573
+ - IP hash / session hash / payload hash
574
+ - provider 名称
575
+ - provider error code
576
+ - Cloudflare Ray ID 等请求追踪 id
577
+
578
+ 禁止记录:
579
+
580
+ - Turnstile secret key
581
+ - provider token
582
+ - action token 原文
583
+ - CSRF token
584
+ - email 原文
585
+ - 用户提交的 prompt / keyword 原文
586
+ - session cookie 原文
587
+
588
+ ## Cloudflare 边缘层建议
589
+
590
+ 如果站点部署在 Cloudflare 后面,建议把边缘层当作第一道粗过滤:
591
+
592
+ - 对公开高成本 API 配置 Cloudflare WAF / Rate Limiting 规则。
593
+ - 对异常国家、ASN、路径、方法做边缘限速或挑战。
594
+ - 使用 Turnstile widget 提供人机验证 token。
595
+ - 只信任来自 Cloudflare 的 `CF-Connecting-IP`、`CF-IPCountry`、`CF-Ray` 等 header;非可信代理来源不得直接消费这些 header。
596
+
597
+ 但正式安全结论仍在源站:
598
+
599
+ - Cloudflare 边缘规则可能被配置绕过、环境未启用或本地测试不存在。
600
+ - 源站必须独立完成 action token、人机验证服务端校验、限流和业务额度判断。
601
+
602
+ ## showinai 落位示例
603
+
604
+ `showinai` 的匿名 GEO 诊断应声明为一个 public action:
605
+
606
+ ```text
607
+ action_id = "showinai.public_geo_run"
608
+ owner_app = "showinai"
609
+ method = "POST"
610
+ route = "showinai:public-geo-runs"
611
+ required gates = ["csrf", "public_action_token", "human_verification", "rate_limit"]
612
+ payload_hash_fields = ["seed_keywords", "business_type", "audience_type", "market_language", "intent_templates"]
613
+ ```
614
+
615
+ 执行链路:
616
+
617
+ 1. `GET /geo-checker/` SSR 输出表单、Turnstile site key、CSRF cookie、`public_action_token`。
618
+ 2. 用户提交到 `POST /api/v1/showinai/public/geo-runs`。
619
+ 3. foundation 验证 gate。
620
+ 4. `showinai.api` 只接收已通过 gate 的请求。
621
+ 5. `showinai.service` 继续判断免费额度、缓存命中、provider 预算和业务输入校验。
622
+
623
+ 这样可以满足“交互只发生在现有网页上”的产品诉求,同时避免把 Cloudflare 校验、nonce、限流和安全日志散落到每个业务 app。
624
+
625
+ ## 实施阶段
626
+
627
+ ### Phase 1:文档与合同
628
+
629
+ - 新增 `PublicActionProtectionManifest` 设计。
630
+ - 定义 `HumanVerificationProvider` 协议。
631
+ - 定义 action token 签发 / 消费 facade。
632
+ - 定义错误码与日志字段。
633
+
634
+ ### Phase 2:foundation 实现
635
+
636
+ - 在 `pz_foundation.settings` 增加 `public_action_gate` schema。
637
+ - 在 `configs/default.jsonc` / `configs/default.env` 增加作者面。
638
+ - 增加 `PublicActionGateRuntime` 作为唯一 runtime owner,在 activate 阶段构造 signer、provider、resolver、limiter、nonce store 和 audit sink。
639
+ - 增加 `ClientIdentityResolver`,统一可信代理、Cloudflare header、ASGI client host、匿名 session 和 IP hash 语义。
640
+ - 增加 `ActionTokenSigner`,替代“业务模块里放 token 生成函数”的做法。
641
+ - 增加 `RateLimitEvaluator` / `RateLimitKeyBuilder` / `CounterStore`,首版底层可复用现有 `KeyLimiter`。
642
+ - 增加 Turnstile provider adapter。
643
+ - 增加 nonce store,优先复用 foundation cache capability。
644
+ - 增加 middleware 或 route dependency,在 RuntimePlan 编译后消费 manifest。
645
+ - 增加契约测试覆盖 token 过期、重放、Turnstile 失败、限流、Redis 不可用。
646
+
647
+ ### Phase 3:业务接入
648
+
649
+ - `showinai` 的公开诊断 action 贡献 action declaration。
650
+ - 页面 SSR 接入 token 签发和 Turnstile site key。
651
+ - API handler 移除私有校验实现,只消费 foundation verdict。
652
+ - 业务 service 保留自己的免费额度、缓存和 provider 成本控制。
653
+
654
+ ### Phase 4:现有实现收敛
655
+
656
+ - `pz_api_access.middlewares.rate_limited`
657
+ - 保留 `pz_api_access` 作为外部 API 访问控制 owner。
658
+ - 迁移 IP 解析、limiter 构造、规则 verdict 到 foundation 的 `ClientIdentityResolver` 与 `RateLimitEvaluator`。
659
+ - `pz_api_access` 只负责把自己的 path/global rule 编译成访问控制 policy。
660
+ - `pz_api_access.middlewares.risk_control`
661
+ - 保留 `ip`、`email`、`ip_email`、`token` 这些业务语义。
662
+ - 迁移 key hash、body field normalization、retry-after verdict 到 foundation key builder。
663
+ - `pz_api_access.deps.require_api_token`
664
+ - PAT 生命周期和 scope 校验仍归 `pz_api_access`。
665
+ - per-token limit 改为消费 foundation `RateLimitEvaluator`,避免同时维护 `_LOCAL_PAT_RATE_LIMITS` 和 `KeyLimiter` 两套语义。
666
+ - `pz_page_center.review_state`
667
+ - review quota / freeze 仍归 Page Center。
668
+ - 如果需要共享 reserve counter,后续只抽取 `CounterStore.reserve(...)` 能力,不把 Page Center 治理规则迁入 foundation。
669
+ - `pz_site_media.services.enforce`
670
+ - 存储 quota 仍归 media。
671
+ - 只在公开上传前置动作需要匿名保护时接入 Public Action Gate;套餐额度不迁移。
672
+
673
+ ## 冻结结论
674
+
675
+ - Public Action Gate 是 foundation 通用能力。
676
+ - Cloudflare Turnstile 是首个 human verification provider,不是业务 app 私有逻辑。
677
+ - 任何高成本公开免费接口都不能只靠 CORS、Referer 或页面 widget 防护。
678
+ - 服务端必须同时验证 action token、人机验证 token、CSRF / origin 和 action 级限流。
679
+ - 业务 app 只声明自己的 action 与业务预算,不复制通用安全链路。
680
+ - 现有 IP 限制、risk control、PAT limit、review quota 和业务 quota 要按 owner 分层收敛,不能简单合并成一个全局杂物中间件。