core-framework 1.4.0__tar.gz → 1.5.0__tar.gz

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 (430) hide show
  1. core_framework-1.5.0/.cursor/rules/code-review-output.mdc +31 -0
  2. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/domain-imports.mdc +17 -1
  3. core_framework-1.5.0/.cursor/rules/domain-services-and-adapters.mdc +47 -0
  4. core_framework-1.5.0/.cursor/rules/exception-handlers.mdc +33 -0
  5. core_framework-1.5.0/.cursor/rules/henry-cursor-rules-sync.mdc +52 -0
  6. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/implementation-workflow.mdc +5 -5
  7. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/layer-boundaries.mdc +4 -2
  8. core_framework-1.5.0/.cursor/rules/structured-logging.mdc +35 -0
  9. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/skills/add-domain/SKILL.md +2 -0
  10. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/skills/code-review/SKILL.md +10 -1
  11. {core_framework-1.4.0 → core_framework-1.5.0}/.github/workflows/_deploy.yml +48 -3
  12. {core_framework-1.4.0 → core_framework-1.5.0}/.gitignore +8 -0
  13. {core_framework-1.4.0 → core_framework-1.5.0}/CHANGELOG.md +36 -2
  14. {core_framework-1.4.0 → core_framework-1.5.0}/PKG-INFO +4 -1
  15. core_framework-1.5.0/alembic/media/alembic/env.py +72 -0
  16. core_framework-1.5.0/alembic/media/alembic/versions/v1_media_init_baseline.py +45 -0
  17. core_framework-1.5.0/alembic/notification/alembic.ini +52 -0
  18. core_framework-1.5.0/alembic/user/alembic/README +1 -0
  19. core_framework-1.5.0/alembic/user/alembic/script.py.mako +28 -0
  20. core_framework-1.5.0/alembic/user/alembic/versions/user_add_avatar_ingest_sequences.py +34 -0
  21. {core_framework-1.4.0 → core_framework-1.5.0}/config.toml +15 -3
  22. {core_framework-1.4.0 → core_framework-1.5.0}/config.toml.template +14 -2
  23. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/admin/users/schemas.py +0 -1
  24. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/dependencies.py +11 -1
  25. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/posts/authenticated/schemas.py +3 -2
  26. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/posts/public/router.py +11 -3
  27. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/posts/schemas.py +0 -1
  28. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/users/authenticated/router.py +55 -1
  29. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/users/authenticated/schemas.py +7 -2
  30. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/users/mappers.py +0 -5
  31. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/bootstrap.py +4 -0
  32. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/comments/public_service.py +91 -52
  33. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/events/event_service.py +2 -2
  34. core_framework-1.5.0/core_framework/application/media/README.md +11 -0
  35. core_framework-1.5.0/core_framework/application/media/avatar_service.py +148 -0
  36. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/moderation/appeal_service.py +10 -15
  37. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/moderation/user_service.py +41 -38
  38. core_framework-1.5.0/core_framework/application/shared/enums.py +21 -0
  39. core_framework-1.5.0/core_framework/application/shared/worker_jobs.py +17 -0
  40. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/users/admin_service.py +4 -2
  41. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/users/authenticated_service.py +2 -0
  42. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/bundled_alembic.py +1 -0
  43. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/exception_handlers/comment.py +10 -25
  44. core_framework-1.5.0/core_framework/core/exception_handlers/common.py +19 -0
  45. core_framework-1.5.0/core_framework/core/exception_handlers/media.py +64 -0
  46. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/exception_handlers/moderation.py +10 -30
  47. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/exception_handlers/notification.py +5 -5
  48. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/exception_handlers/post.py +7 -13
  49. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/exception_handlers/setup.py +8 -24
  50. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/exception_handlers/user.py +8 -20
  51. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/redis.py +5 -1
  52. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/runtime.py +3 -3
  53. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/settings.py +68 -11
  54. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/comment/dependencies.py +2 -2
  55. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/comment/models.py +2 -0
  56. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/comment/repository.py +10 -0
  57. core_framework-1.5.0/core_framework/domains/media/README.md +25 -0
  58. core_framework-1.5.0/core_framework/domains/media/__init__.py +30 -0
  59. core_framework-1.5.0/core_framework/domains/media/avatar/components/finals_publisher.py +16 -0
  60. core_framework-1.5.0/core_framework/domains/media/avatar/components/staging.py +51 -0
  61. core_framework-1.5.0/core_framework/domains/media/avatar/components/variant_encoder.py +15 -0
  62. core_framework-1.5.0/core_framework/domains/media/avatar/constants.py +26 -0
  63. core_framework-1.5.0/core_framework/domains/media/avatar/exceptions.py +26 -0
  64. core_framework-1.5.0/core_framework/domains/media/avatar/models.py +24 -0
  65. core_framework-1.5.0/core_framework/domains/media/avatar/ports/finals_publisher.py +7 -0
  66. core_framework-1.5.0/core_framework/domains/media/avatar/ports/staging.py +10 -0
  67. core_framework-1.5.0/core_framework/domains/media/avatar/ports/variant_encoder.py +7 -0
  68. core_framework-1.5.0/core_framework/domains/media/avatar/service.py +162 -0
  69. core_framework-1.5.0/core_framework/domains/media/dependencies.py +82 -0
  70. core_framework-1.5.0/core_framework/domains/media/shared/exceptions.py +6 -0
  71. core_framework-1.5.0/core_framework/domains/media/shared/repository.py +179 -0
  72. core_framework-1.5.0/core_framework/domains/media/shared/service.py +6 -0
  73. core_framework-1.5.0/core_framework/domains/media/shared/utils.py +21 -0
  74. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/moderation/dependencies.py +2 -2
  75. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/moderation/service.py +4 -0
  76. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/notification/dependencies.py +2 -2
  77. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/notification/repository.py +8 -6
  78. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/notification/service.py +2 -2
  79. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/post/constants.py +2 -0
  80. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/post/dependencies.py +2 -2
  81. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/post/repository.py +97 -8
  82. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/post/service.py +18 -1
  83. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/user/__init__.py +2 -0
  84. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/user/dependencies.py +2 -2
  85. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/user/models.py +6 -2
  86. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/user/repository.py +66 -7
  87. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/user/service.py +23 -0
  88. core_framework-1.5.0/core_framework/infrastructure/media/local_staging_adapter.py +61 -0
  89. core_framework-1.5.0/core_framework/infrastructure/media/pillow_variant_encoder_adapter.py +45 -0
  90. core_framework-1.5.0/core_framework/infrastructure/media/ssh_finals_publisher_adapter.py +101 -0
  91. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/main.py +3 -3
  92. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/testing/arq.py +2 -1
  93. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/testing/auth.py +37 -0
  94. core_framework-1.5.0/core_framework/testing/media.py +81 -0
  95. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/worker/main.py +4 -3
  96. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/worker/schedules/__init__.py +10 -0
  97. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/worker/schedules/schedule_aggregate_comment_stats.py +8 -5
  98. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/worker/schedules/schedule_aggregate_post_stats.py +6 -5
  99. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/worker/schedules/schedule_aggregate_user_stats.py +6 -5
  100. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/worker/schedules/schedule_expired_account_deletions.py +4 -3
  101. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/worker/schedules/schedule_expired_mute_lifts.py +4 -3
  102. core_framework-1.5.0/core_framework/worker/schedules/schedule_sweep_stale_avatar_staging.py +14 -0
  103. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/worker/tasks/__init__.py +4 -0
  104. core_framework-1.5.0/core_framework/worker/tasks/delete_superseded_avatar_finals.py +16 -0
  105. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/worker/tasks/process_account_deletion.py +1 -1
  106. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/worker/tasks/process_aggregate_comment_stats.py +2 -2
  107. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/worker/tasks/process_aggregate_post_stats.py +2 -2
  108. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/worker/tasks/process_aggregate_user_stats.py +2 -2
  109. core_framework-1.5.0/core_framework/worker/tasks/process_media_asset_variants.py +31 -0
  110. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/worker/tasks/process_mute_lift.py +1 -1
  111. {core_framework-1.4.0 → core_framework-1.5.0}/docker-compose.yml +4 -0
  112. core_framework-1.5.0/docs/README.md +22 -0
  113. {core_framework-1.4.0 → core_framework-1.5.0}/docs/api.md +1 -0
  114. {core_framework-1.4.0 → core_framework-1.5.0}/docs/architecture.md +13 -5
  115. {core_framework-1.4.0 → core_framework-1.5.0}/docs/conventions.md +1 -0
  116. core_framework-1.5.0/docs/deployment/Caddyfile.example +24 -0
  117. core_framework-1.5.0/docs/deployment/edge-upload-limits.md +59 -0
  118. core_framework-1.5.0/docs/deployment/image-server-known-hosts.example +5 -0
  119. core_framework-1.5.0/docs/deployment/image-server-ssh.md +84 -0
  120. core_framework-1.5.0/docs/domain-services-and-adapters.md +179 -0
  121. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/comments/retrieve_comments.md +4 -2
  122. core_framework-1.5.0/docs/flows/media/upload_pipeline.md +37 -0
  123. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/notifications/notification_inbox.md +1 -1
  124. core_framework-1.5.0/docs/flows/posts/post_visibility.md +75 -0
  125. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/users/account.md +14 -3
  126. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/users/avatar.md +15 -12
  127. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/users/change_history.md +1 -1
  128. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/users/my_posts_and_comments.md +9 -4
  129. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/users/preferences.md +4 -4
  130. core_framework-1.5.0/docs/flows/users/profile.md +142 -0
  131. {core_framework-1.4.0 → core_framework-1.5.0}/docs/follow-system-design.md +5 -3
  132. core_framework-1.4.0/docs/flows/media/upload_pipeline.md → core_framework-1.5.0/docs/media-upload-pipeline-design.md +87 -82
  133. {core_framework-1.4.0 → core_framework-1.5.0}/docs/testing-plugin-design.md +8 -9
  134. {core_framework-1.4.0 → core_framework-1.5.0}/docs/todo.md +2 -1
  135. {core_framework-1.4.0 → core_framework-1.5.0}/pyproject.toml +4 -1
  136. {core_framework-1.4.0 → core_framework-1.5.0}/tests/conftest.py +10 -2
  137. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/api/admin/users/router_test.py +10 -10
  138. core_framework-1.5.0/tests/integration/api/users/authenticated/avatar_upload_test.py +165 -0
  139. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/api/users/authenticated/router_test.py +79 -31
  140. core_framework-1.5.0/tests/integration/api/users/public/__init__.py +0 -0
  141. core_framework-1.5.0/tests/integration/worker/__init__.py +0 -0
  142. core_framework-1.5.0/tests/integration/worker/avatar_upload_test.py +167 -0
  143. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/worker/conftest.py +1 -1
  144. core_framework-1.5.0/tests/unit/__init__.py +0 -0
  145. core_framework-1.5.0/tests/unit/application/comments/__init__.py +0 -0
  146. core_framework-1.5.0/tests/unit/domains/__init__.py +0 -0
  147. core_framework-1.5.0/tests/unit/domains/comment/__init__.py +0 -0
  148. core_framework-1.5.0/tests/unit/infrastructure/__init__.py +0 -0
  149. core_framework-1.5.0/tests/unit/infrastructure/media/__init__.py +0 -0
  150. core_framework-1.5.0/tests/unit/infrastructure/media/ssh_finals_publisher_adapter_test.py +279 -0
  151. {core_framework-1.4.0 → core_framework-1.5.0}/uv.lock +62 -1
  152. core_framework-1.4.0/core_framework/application/shared/enums.py +0 -11
  153. core_framework-1.4.0/core_framework/core/exception_handlers/common.py +0 -5
  154. core_framework-1.4.0/docs/flows/users/profile.md +0 -89
  155. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/api-layer.mdc +0 -0
  156. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/api-reference-docs.mdc +0 -0
  157. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/api-security.mdc +0 -0
  158. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/api-validation.mdc +0 -0
  159. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/application-layer.mdc +0 -0
  160. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/constants-final.mdc +0 -0
  161. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/database-triggers.md +0 -0
  162. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/domain-caller-context.mdc +0 -0
  163. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/domain-input-guards.mdc +0 -0
  164. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/domain-repository-exceptions.mdc +0 -0
  165. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/flow-documentation.mdc +0 -0
  166. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/integration-test-strategy.mdc +0 -0
  167. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/no-code-in-docs.mdc +0 -0
  168. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/no-docstrings.mdc +0 -0
  169. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/postgres-config-conventions.mdc +0 -0
  170. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/repository-read-consistency.mdc +0 -0
  171. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/strong-read-opt-in.mdc +0 -0
  172. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/rules/tech-stack.mdc +0 -0
  173. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/skills/add-config/SKILL.md +0 -0
  174. {core_framework-1.4.0 → core_framework-1.5.0}/.cursor/skills/recommend-features/SKILL.md +0 -0
  175. {core_framework-1.4.0 → core_framework-1.5.0}/.dockerignore +0 -0
  176. {core_framework-1.4.0 → core_framework-1.5.0}/.github/workflows/dev-ci-cd.yaml +0 -0
  177. {core_framework-1.4.0 → core_framework-1.5.0}/.github/workflows/manual-deployment.yaml +0 -0
  178. {core_framework-1.4.0 → core_framework-1.5.0}/.github/workflows/publish-pypi.yml +0 -0
  179. {core_framework-1.4.0 → core_framework-1.5.0}/.github/workflows/test.yaml +0 -0
  180. {core_framework-1.4.0 → core_framework-1.5.0}/.pre-commit-config.yaml +0 -0
  181. {core_framework-1.4.0 → core_framework-1.5.0}/.python-version +0 -0
  182. {core_framework-1.4.0 → core_framework-1.5.0}/LICENSE +0 -0
  183. {core_framework-1.4.0 → core_framework-1.5.0}/README.md +0 -0
  184. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/comment/alembic/README +0 -0
  185. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/comment/alembic/env.py +0 -0
  186. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/comment/alembic/script.py.mako +0 -0
  187. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/comment/alembic/versions/v1_comment_init_baseline.py +0 -0
  188. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/comment/alembic.ini +0 -0
  189. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/extension/alembic/README +0 -0
  190. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/extension/alembic/env.py +0 -0
  191. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/extension/alembic/script.py.mako +0 -0
  192. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/extension/alembic/versions/v1_ext_init_baseline.py +0 -0
  193. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/extension/alembic.ini +0 -0
  194. {core_framework-1.4.0/alembic/moderation → core_framework-1.5.0/alembic/media}/alembic/README +0 -0
  195. {core_framework-1.4.0/alembic/moderation → core_framework-1.5.0/alembic/media}/alembic/script.py.mako +0 -0
  196. {core_framework-1.4.0/alembic/notification → core_framework-1.5.0/alembic/media}/alembic.ini +0 -0
  197. {core_framework-1.4.0/alembic/notification → core_framework-1.5.0/alembic/moderation}/alembic/README +0 -0
  198. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/moderation/alembic/env.py +0 -0
  199. {core_framework-1.4.0/alembic/notification → core_framework-1.5.0/alembic/moderation}/alembic/script.py.mako +0 -0
  200. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/moderation/alembic/versions/v1_mod_init_baseline.py +0 -0
  201. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/moderation/alembic.ini +0 -0
  202. {core_framework-1.4.0/alembic/post → core_framework-1.5.0/alembic/notification}/alembic/README +0 -0
  203. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/notification/alembic/env.py +0 -0
  204. {core_framework-1.4.0/alembic/post → core_framework-1.5.0/alembic/notification}/alembic/script.py.mako +0 -0
  205. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/notification/alembic/versions/v1_notif_init_baseline.py +0 -0
  206. {core_framework-1.4.0/alembic/user → core_framework-1.5.0/alembic/post}/alembic/README +0 -0
  207. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/post/alembic/env.py +0 -0
  208. {core_framework-1.4.0/alembic/user → core_framework-1.5.0/alembic/post}/alembic/script.py.mako +0 -0
  209. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/post/alembic/versions/v1_post_init_baseline.py +0 -0
  210. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/post/alembic.ini +0 -0
  211. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/user/alembic/env.py +0 -0
  212. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/user/alembic/versions/user_add_date_of_birth_to_profiles.py +0 -0
  213. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/user/alembic/versions/v1_user_init_baseline.py +0 -0
  214. {core_framework-1.4.0 → core_framework-1.5.0}/alembic/user/alembic.ini +0 -0
  215. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/__init__.py +0 -0
  216. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/__init__.py +0 -0
  217. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/admin/__init__.py +0 -0
  218. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/admin/comments/router.py +0 -0
  219. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/admin/comments/schemas.py +0 -0
  220. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/admin/moderation/__init__.py +0 -0
  221. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/admin/moderation/router.py +0 -0
  222. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/admin/moderation/schemas.py +0 -0
  223. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/admin/posts/router.py +0 -0
  224. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/admin/posts/schemas.py +0 -0
  225. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/admin/router.py +0 -0
  226. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/admin/users/__init__.py +0 -0
  227. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/admin/users/router.py +0 -0
  228. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/auth/__init__.py +0 -0
  229. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/auth/router.py +0 -0
  230. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/auth/schemas.py +0 -0
  231. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/comments/authenticated/mappers.py +0 -0
  232. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/comments/authenticated/router.py +0 -0
  233. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/comments/authenticated/schemas.py +0 -0
  234. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/comments/public/router.py +0 -0
  235. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/comments/public/schemas.py +0 -0
  236. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/comments/router.py +0 -0
  237. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/comments/schemas.py +0 -0
  238. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/constants.py +0 -0
  239. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/events/router.py +0 -0
  240. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/events/schemas.py +0 -0
  241. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/notifications/authenticated/router.py +0 -0
  242. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/notifications/authenticated/schemas.py +0 -0
  243. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/notifications/router.py +0 -0
  244. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/posts/authenticated/mappers.py +0 -0
  245. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/posts/authenticated/router.py +0 -0
  246. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/posts/public/schemas.py +0 -0
  247. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/posts/router.py +0 -0
  248. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/router.py +0 -0
  249. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/schemas.py +0 -0
  250. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/system/__init__.py +0 -0
  251. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/system/router.py +0 -0
  252. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/users/__init__.py +0 -0
  253. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/users/authenticated/__init__.py +0 -0
  254. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/users/authenticated/mappers.py +0 -0
  255. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/users/public/__init__.py +0 -0
  256. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/users/public/router.py +0 -0
  257. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/users/public/schemas.py +0 -0
  258. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/users/router.py +0 -0
  259. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/api/users/shared/schemas.py +0 -0
  260. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/__init__.py +0 -0
  261. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/auth/__init__.py +0 -0
  262. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/auth/access_service.py +0 -0
  263. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/auth/auth_service.py +0 -0
  264. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/auth/models.py +0 -0
  265. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/comments/admin_service.py +0 -0
  266. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/comments/aggregation_service.py +0 -0
  267. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/comments/authenticated_service.py +0 -0
  268. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/events/README.md +0 -0
  269. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/events/event_token.py +0 -0
  270. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/events/models.py +0 -0
  271. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/moderation/__init__.py +0 -0
  272. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/moderation/moderator_service.py +0 -0
  273. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/moderation/report_service.py +0 -0
  274. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/moderation/scheduled_service.py +0 -0
  275. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/notifications/README.md +0 -0
  276. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/notifications/enums.py +0 -0
  277. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/notifications/inbox_service.py +0 -0
  278. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/notifications/mute_service.py +0 -0
  279. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/notifications/notification_service.py +0 -0
  280. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/posts/admin_service.py +0 -0
  281. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/posts/aggregation_service.py +0 -0
  282. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/posts/authenticated_service.py +0 -0
  283. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/posts/public_service.py +0 -0
  284. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/shared/__init__.py +0 -0
  285. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/shared/exceptions.py +0 -0
  286. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/shared/user_agent.py +0 -0
  287. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/users/__init__.py +0 -0
  288. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/users/aggregation_service.py +0 -0
  289. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/users/public_service.py +0 -0
  290. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/application/users/scheduled_service.py +0 -0
  291. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/asgi.py +0 -0
  292. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/constants.py +0 -0
  293. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/__init__.py +0 -0
  294. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/cache.py +0 -0
  295. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/context.py +0 -0
  296. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/database.py +0 -0
  297. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/exception_handlers/__init__.py +0 -0
  298. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/firebase.py +0 -0
  299. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/http_client.py +0 -0
  300. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/logging.py +0 -0
  301. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/middleware.py +0 -0
  302. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/observability.py +0 -0
  303. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/core/pagination.py +0 -0
  304. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/__init__.py +0 -0
  305. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/comment/README.md +0 -0
  306. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/comment/__init__.py +0 -0
  307. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/comment/constants.py +0 -0
  308. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/comment/enums.py +0 -0
  309. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/comment/exceptions.py +0 -0
  310. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/comment/service.py +0 -0
  311. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/exceptions.py +0 -0
  312. {core_framework-1.4.0/core_framework/worker → core_framework-1.5.0/core_framework/domains/media/avatar}/__init__.py +0 -0
  313. {core_framework-1.4.0/tests → core_framework-1.5.0/core_framework/domains/media/avatar/components}/__init__.py +0 -0
  314. {core_framework-1.4.0/tests/integration → core_framework-1.5.0/core_framework/domains/media/avatar/ports}/__init__.py +0 -0
  315. {core_framework-1.4.0/tests/integration/api → core_framework-1.5.0/core_framework/domains/media/shared}/__init__.py +0 -0
  316. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/moderation/README.md +0 -0
  317. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/moderation/__init__.py +0 -0
  318. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/moderation/enums.py +0 -0
  319. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/moderation/exceptions.py +0 -0
  320. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/moderation/models.py +0 -0
  321. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/moderation/repository.py +0 -0
  322. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/notification/README.md +0 -0
  323. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/notification/__init__.py +0 -0
  324. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/notification/enums.py +0 -0
  325. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/notification/exceptions.py +0 -0
  326. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/notification/models.py +0 -0
  327. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/post/README.md +0 -0
  328. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/post/__init__.py +0 -0
  329. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/post/enums.py +0 -0
  330. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/post/exceptions.py +0 -0
  331. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/post/models.py +0 -0
  332. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/user/README.md +0 -0
  333. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/user/constants.py +0 -0
  334. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/user/enums.py +0 -0
  335. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/user/exceptions.py +0 -0
  336. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/user/utils.py +0 -0
  337. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/domains/utils.py +0 -0
  338. {core_framework-1.4.0/tests/integration/api/admin → core_framework-1.5.0/core_framework/infrastructure}/__init__.py +0 -0
  339. {core_framework-1.4.0/tests/integration/api/admin/comments → core_framework-1.5.0/core_framework/infrastructure/media}/__init__.py +0 -0
  340. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/migrate_cli.py +0 -0
  341. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/testing/__init__.py +0 -0
  342. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/testing/config.py +0 -0
  343. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/testing/containers.py +0 -0
  344. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/testing/firebase.py +0 -0
  345. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/testing/hookspecs.py +0 -0
  346. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/testing/httpx_test_client.py +0 -0
  347. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/testing/migrations.py +0 -0
  348. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/testing/plugin.py +0 -0
  349. {core_framework-1.4.0/tests/integration/api/admin/moderation → core_framework-1.5.0/core_framework/worker}/__init__.py +0 -0
  350. {core_framework-1.4.0 → core_framework-1.5.0}/core_framework/worker/worker_context.py +0 -0
  351. {core_framework-1.4.0 → core_framework-1.5.0}/docker-compose.dev.yaml +0 -0
  352. {core_framework-1.4.0 → core_framework-1.5.0}/dockerfile +0 -0
  353. {core_framework-1.4.0 → core_framework-1.5.0}/docs/architecture-decisions.md +0 -0
  354. {core_framework-1.4.0 → core_framework-1.5.0}/docs/core-framework-migration.md +0 -0
  355. {core_framework-1.4.0 → core_framework-1.5.0}/docs/database-triggers.md +0 -0
  356. {core_framework-1.4.0 → core_framework-1.5.0}/docs/event-outbox-design.md +0 -0
  357. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/auth/access_control.md +0 -0
  358. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/auth/registration.md +0 -0
  359. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/comments/admin_comments.md +0 -0
  360. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/comments/comment_report.md +0 -0
  361. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/comments/comment_stats_aggregation.md +0 -0
  362. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/comments/create_comment.md +0 -0
  363. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/comments/delete_comment.md +0 -0
  364. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/comments/edit_comment.md +0 -0
  365. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/events/events.md +0 -0
  366. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/mentions/mentions_in_content.md +0 -0
  367. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/moderation/appeals.md +0 -0
  368. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/moderation/internal_notes.md +0 -0
  369. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/moderation/moderator_actions.md +0 -0
  370. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/moderation/reports.md +0 -0
  371. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/moderation/restrictions.md +0 -0
  372. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/posts/admin_posts.md +0 -0
  373. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/posts/author_context.md +0 -0
  374. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/posts/hashtag_discovery.md +0 -0
  375. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/posts/post_like.md +0 -0
  376. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/posts/post_stats_aggregation.md +0 -0
  377. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/users/account_deletion.md +0 -0
  378. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/users/blocks.md +0 -0
  379. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/users/check_username_exists.md +0 -0
  380. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/users/follow.md +0 -0
  381. {core_framework-1.4.0 → core_framework-1.5.0}/docs/flows/users/user_removal.md +0 -0
  382. {core_framework-1.4.0 → core_framework-1.5.0}/docs/package-api.md +0 -0
  383. {core_framework-1.4.0 → core_framework-1.5.0}/docs/patch-update-typed-payload.md +0 -0
  384. {core_framework-1.4.0 → core_framework-1.5.0}/firebase_config.example.json +0 -0
  385. {core_framework-1.4.0 → core_framework-1.5.0}/makefile +0 -0
  386. {core_framework-1.4.0/tests/integration/api/admin/posts → core_framework-1.5.0/tests}/__init__.py +0 -0
  387. {core_framework-1.4.0/tests/integration/api/admin/users → core_framework-1.5.0/tests/integration}/__init__.py +0 -0
  388. {core_framework-1.4.0/tests/integration/api/auth → core_framework-1.5.0/tests/integration/api}/__init__.py +0 -0
  389. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/api/_http_helpers.py +0 -0
  390. {core_framework-1.4.0/tests/integration/api/comments → core_framework-1.5.0/tests/integration/api/admin}/__init__.py +0 -0
  391. {core_framework-1.4.0/tests/integration/api/comments/public → core_framework-1.5.0/tests/integration/api/admin/comments}/__init__.py +0 -0
  392. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/api/admin/comments/router_test.py +0 -0
  393. {core_framework-1.4.0/tests/integration/api/notifications → core_framework-1.5.0/tests/integration/api/admin/moderation}/__init__.py +0 -0
  394. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/api/admin/moderation/router_test.py +0 -0
  395. {core_framework-1.4.0/tests/integration/api → core_framework-1.5.0/tests/integration/api/admin}/posts/__init__.py +0 -0
  396. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/api/admin/posts/router_test.py +0 -0
  397. {core_framework-1.4.0/tests/integration/api/posts/public → core_framework-1.5.0/tests/integration/api/admin/users}/__init__.py +0 -0
  398. {core_framework-1.4.0/tests/integration/api/system → core_framework-1.5.0/tests/integration/api/auth}/__init__.py +0 -0
  399. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/api/auth/router_test.py +0 -0
  400. {core_framework-1.4.0/tests/integration/api/users → core_framework-1.5.0/tests/integration/api/comments}/__init__.py +0 -0
  401. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/api/comments/authenticated/comment_writes_integration_test.py +0 -0
  402. {core_framework-1.4.0/tests/integration/api/users/authenticated → core_framework-1.5.0/tests/integration/api/comments/public}/__init__.py +0 -0
  403. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/api/comments/public/router_test.py +0 -0
  404. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/api/events/router_test.py +0 -0
  405. {core_framework-1.4.0/tests/integration/api/users/public → core_framework-1.5.0/tests/integration/api/notifications}/__init__.py +0 -0
  406. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/api/notifications/router_test.py +0 -0
  407. {core_framework-1.4.0/tests/integration/worker → core_framework-1.5.0/tests/integration/api/posts}/__init__.py +0 -0
  408. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/api/posts/authenticated/post_writes_integration_test.py +0 -0
  409. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/api/posts/comment_count_aggregation_test.py +0 -0
  410. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/api/posts/followers_visibility_test.py +0 -0
  411. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/api/posts/post_stats_dirty_marking_test.py +0 -0
  412. {core_framework-1.4.0/tests/unit → core_framework-1.5.0/tests/integration/api/posts/public}/__init__.py +0 -0
  413. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/api/posts/public/router_test.py +0 -0
  414. {core_framework-1.4.0/tests/unit/application/comments → core_framework-1.5.0/tests/integration/api/system}/__init__.py +0 -0
  415. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/api/system/router_test.py +0 -0
  416. {core_framework-1.4.0/tests/unit/domains → core_framework-1.5.0/tests/integration/api/users}/__init__.py +0 -0
  417. {core_framework-1.4.0/tests/unit/domains/comment → core_framework-1.5.0/tests/integration/api/users/authenticated}/__init__.py +0 -0
  418. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/api/users/public/router_test.py +0 -0
  419. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/worker/account_deletion_test.py +0 -0
  420. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/worker/aggregate_comment_stats_test.py +0 -0
  421. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/worker/aggregate_post_stats_test.py +0 -0
  422. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/worker/aggregate_user_stats_test.py +0 -0
  423. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/worker/mute_lift_test.py +0 -0
  424. {core_framework-1.4.0 → core_framework-1.5.0}/tests/integration/worker/utils_test.py +0 -0
  425. {core_framework-1.4.0 → core_framework-1.5.0}/tests/unit/application/events/event_service_test.py +0 -0
  426. {core_framework-1.4.0 → core_framework-1.5.0}/tests/unit/application/notifications/inbox_service_test.py +0 -0
  427. {core_framework-1.4.0 → core_framework-1.5.0}/tests/unit/core/bundled_alembic_test.py +0 -0
  428. {core_framework-1.4.0 → core_framework-1.5.0}/tests/unit/core/migrate_cli_test.py +0 -0
  429. {core_framework-1.4.0 → core_framework-1.5.0}/tests/unit/core/pagination_test.py +0 -0
  430. {core_framework-1.4.0 → core_framework-1.5.0}/tests/unit/domains/user/service_test.py +0 -0
@@ -0,0 +1,31 @@
1
+ ---
2
+ description: When the user asks for a code review or audit, report only issues and improvements—not strengths
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Code review output
7
+
8
+ When the user asks for a **review**, **audit**, or similar (e.g. "review my code", "comprehensive review", "what's wrong with"):
9
+
10
+ ## Report only
11
+
12
+ - **Bugs** and correctness risks
13
+ - **Wrong** behavior vs design docs, API contracts, or project rules
14
+ - **Improvements** and **optimizations** worth making
15
+ - **Contradictions** between code and documented intent (including doc drift)
16
+
17
+ Prioritize by severity. Include enough context (file, behavior, suggested fix direction) to act on each item.
18
+
19
+ ## Do not report
20
+
21
+ - Sections praising what is **solid**, **well done**, or **correctly implemented**
22
+ - **"What's good"** / **"Overall assessment"** summaries that restate strengths
23
+ - Long **checklist pass** narratives ("layering is clean", "DI is wired correctly") unless a listed item is actually broken
24
+
25
+ A one-line **scope** (what was reviewed) is fine. If nothing actionable is found, say so briefly—do not pad with compliments.
26
+
27
+ ## Accepted trade-offs
28
+
29
+ Do **not** list documented accepted trade-offs as findings unless the user asked about them or the implementation **violates** the documented trade-off (then report the violation).
30
+
31
+ Follow the **code-review** skill for *how* to review (docs to read, checklists); this rule governs *what to include in the answer*.
@@ -57,6 +57,15 @@ from core_framework.domains.user.service import UserService # ❌
57
57
  4. **Consistency across adapters**: API and application layers follow the same domain import contract
58
58
  5. **Dependency direction**: Prevents tight coupling to internal implementation details
59
59
 
60
+ ## Domain `__init__.py` export policy
61
+
62
+ Export a symbol from a domain's **`__init__.py`** only when it is consumed **outside** that domain package — for example API, application, infrastructure adapters, exception handlers, or workers (via application services).
63
+
64
+ - **Do export**: exceptions, constants, and models that outer layers or adapters import.
65
+ - **Do not export**: persistence/registry shapes, repository row mappings, and other types used only inside **`domains/<name>/`** (repository, service, components). Import those from submodule paths within the domain.
66
+
67
+ When adding a new model or exception, default to **not** exporting; add to **`__init__.py`** only when an outer layer needs it.
68
+
60
69
  ## Available Domain Exports
61
70
 
62
71
  ### Moderation Domain (`from core_framework.domains.moderation import ...`)
@@ -72,11 +81,18 @@ from core_framework.domains.user.service import UserService # ❌
72
81
  - **Service**: Use `user_service` from `dependencies.py`
73
82
  - **Internal** (do not import from API layer): `DomainUserNotFoundException` — handled within domain; API uses `None` return values
74
83
 
84
+ ### Media Domain (`from core_framework.domains.media import ...`)
85
+ - **Constants**: `AVATAR_INGEST_MAX_BYTES`, `AVATAR_INGEST_UPLOAD_CHUNK_BYTES`, `AVATAR_FINAL_VARIANT_128`, `AVATAR_FINAL_VARIANT_500`
86
+ - **Models**: `AvatarIngestRegistration`, `AvatarVariantBytes`
87
+ - **Exceptions**: `BaseMediaException`, `AvatarIngestBodyTooLargeException`, `AvatarIngestLeaseConflictException`, `AvatarIngestUnsupportedContentTypeException`, `AvatarStagingBlobNotFoundException`, `AvatarFinalsPublishException`
88
+ - **Service**: Use `avatar_service` / `media_service` from `dependencies.py`
89
+ - **Internal** (not in `__init__.py`): `AvatarStagingRegistryEntry` — repository and `AvatarService` only; application uses service methods, not the registry row type
90
+
75
91
  ## How to Fix Violations
76
92
 
77
93
  If you need something that's not exported in `__init__.py`:
78
94
 
79
- 1. **Check if it should be public**: If it's needed by the API layer, add it to the domain's `__init__.py`
95
+ 1. **Check if it should be public**: If an outer layer (API, application, infrastructure adapter, exception handler) needs it, add it to the domain's `__init__.py`. If only repository/service code inside the domain needs it, keep it internal.
80
96
  2. **Reconsider the design**: If it's internal implementation, maybe the API layer shouldn't access it directly
81
97
  3. **Use the service**: Most domain logic should be accessed through `{domain}_service` from `dependencies.py`
82
98
 
@@ -0,0 +1,47 @@
1
+ ---
2
+ description: Domain bounded contexts may use multiple services and ports; application orchestrates; repositories stay Postgres-only
3
+ globs:
4
+ - core_framework/domains/**/*.py
5
+ - core_framework/application/**/*.py
6
+ ---
7
+
8
+ # Domain Services and Adapters
9
+
10
+ See **`docs/domain-services-and-adapters.md`** for the full model. Apply when adding or changing domain packages and application orchestration.
11
+
12
+ ## Application = orchestration
13
+
14
+ - **`core_framework/application/...`** sequences multi-step use cases shared by API and worker (call domain services, enqueue jobs, coordinate cross-domain steps).
15
+ - Do **not** move shared orchestration into API routers or worker handlers when it is reused across transports.
16
+ - Do **not** treat application modules as the long-term home for concrete filesystem/HTTP/SSH clients—use domain ports + components + adapters (below).
17
+
18
+ ## Domain ≠ database only
19
+
20
+ - A bounded context owns **business rules and contracts** for all of its capabilities, not only its Postgres schema.
21
+ - **Repositories** remain **Postgres-only** (SQL in repository + row mapping).
22
+ - **Non-SQL I/O** uses **ports**, **components**, and **adapters**—not `*Repository`.
23
+
24
+ ## Multiple services per domain
25
+
26
+ - Facade **`*Service`** is wired in `dependencies`; internal **`*Component`** classes wrap ports.
27
+ - Do not mix SQL, `aiofiles`, HTTP, or SSH in one class.
28
+ - Application resolves facade services via **`import ...dependencies as ..._deps`**.
29
+
30
+ ## Ports, components, and adapters
31
+
32
+ - **Port** — `*Port` in `domains/<capability>/ports/`; no I/O imports.
33
+ - **Component** — `*Component` in `domains/<capability>/components/`; port + domain rules; composed by facade `*Service`.
34
+ - **Adapter** — `*Adapter` in `infrastructure/<domain>/`; transport prefix when multiple impls.
35
+ - Wire adapters only in `dependencies.py`; application uses facade `*_deps` services.
36
+
37
+ ## Prohibited
38
+
39
+ - SQL and filesystem/HTTP/SSH in the same **repository** or god **service** class.
40
+ - Domain imports from `core_framework.api`, `core_framework.worker`, or `core_framework.application`.
41
+ - Infrastructure imports in domain modules except **`dependencies.py`** (existing rule).
42
+
43
+ ## Media ingest (v1)
44
+
45
+ - **Layout:** `domains/media/shared/`, `avatar/ports/`, `avatar/components/`, `AvatarService`.
46
+ - **Avatar:** `media_deps.avatar_service` only (not individual components).
47
+ - **Cross-domain:** `avatar_ingest_sequence` / profile `avatar_id` via **user** domain; orchestration in `application/media/avatar_service`.
@@ -0,0 +1,33 @@
1
+ ---
2
+ description: FastAPI exception handler registration and typing conventions
3
+ globs:
4
+ - core_framework/core/exception_handlers/**/*.py
5
+ ---
6
+
7
+ # Exception Handlers
8
+
9
+ Domain-specific HTTP exception handlers live under **`core_framework/core/exception_handlers/`**.
10
+
11
+ ## Registration
12
+
13
+ - Register handlers with **`register_exception_handler`** from **`core_framework.core.exception_handlers.common`**.
14
+ - Do **not** call **`app.add_exception_handler`** directly from domain handler modules or **`setup.py`**.
15
+ - Starlette’s **`ExceptionHandler`** type is wider than narrowly typed handlers; the single **`ty: ignore[invalid-argument-type]`** belongs in **`common.register_exception_handler`** only.
16
+
17
+ ## Handler functions
18
+
19
+ - One async handler per mapped exception type: **`(Request, ExcT) -> JSONResponse`**.
20
+ - Type **`exc`** as the **specific** domain exception (not bare **`Exception`**) so handler bodies stay narrow.
21
+ - Map domain exceptions to HTTP status codes; use **`exc.message`** in **`{"detail": ...}`** for client-facing errors.
22
+ - Register **specific** exceptions before domain **base** catch-alls (e.g. **`BasePostException`** last in that module).
23
+
24
+ ## Module layout
25
+
26
+ - One module per domain (e.g. **`post.py`**, **`media.py`**) exporting **`add_*_exception_handlers(app)`**.
27
+ - **`setup.py`** registers cross-cutting handlers, then calls each domain **`add_*_exception_handlers`**.
28
+ - Base domain handlers return **`GENERIC_INTERNAL_SERVER_ERROR_DETAIL`** — not raw exception messages.
29
+
30
+ ## Avoid
31
+
32
+ - Per-call-site **`ty: ignore[invalid-argument-type]`** or **`cast(ExceptionHandler, ...)`** on registrations.
33
+ - Business logic or repository calls inside handlers — translate exception → HTTP response only.
@@ -0,0 +1,52 @@
1
+ ---
2
+ description: When editing Cursor rules or skills in core-framework, sync applicable changes to henry-backend
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Henry cursor rules sync
7
+
8
+ **Henry backend** embeds **core-framework** and shares most **`.cursor/rules/`** and **`.cursor/skills/`** content. Treat both repos as one rules surface unless the user scopes work to core-framework only.
9
+
10
+ ## When this applies
11
+
12
+ Whenever you **add, edit, or remove** a file under **`.cursor/rules/`** or **`.cursor/skills/`** in **this** repository — including new always-on rules, skill workflow updates, and convention changes from feature work (e.g. media/avatar).
13
+
14
+ ## Required follow-up
15
+
16
+ 1. **Sync to henry-backend** in the **same task** or as the **immediate next step** after the user accepts the core-framework rule change.
17
+ 2. Diff by **basename** against **`henry-backend/.cursor/`** (sibling repo or second workspace root).
18
+ 3. Mention in your response that henry-backend was synced, or that it still needs sync if you stopped at core-only per user scope.
19
+
20
+ ## How to sync
21
+
22
+ - **Shared rules/skills** — port into henry-backend and **adapt** for the host:
23
+ - globs: **`core_framework/**`** → **`src/**`** where host code applies
24
+ - imports: **`core_framework....`** vs **`src....`**
25
+ - strong reads: **`strong-read-opt-in`** → **`host-strong-read-opt-in`**
26
+ - host boundaries: reference **`henry-host-context`**
27
+ - **Do not overwrite** henry-only files:
28
+ - **`.cursor/rules/henry-host-context.mdc`**
29
+ - **`.cursor/rules/host-strong-read-opt-in.mdc`**
30
+ - **`.cursor/skills/add-host-domain/SKILL.md`**
31
+ - **Henry-only equivalents** — if core adds a rule with core-only globs (e.g. **`domain-input-guards`** on **`core_framework/**`**), ensure henry has coverage for **`src/**`** via an existing or new host-scoped rule.
32
+
33
+ ## Related docs
34
+
35
+ When a rule encodes a new convention, check whether **`docs/`** in both repos need the same story.
36
+
37
+ **Core `docs/` must stay host-agnostic** — no references to Henry, henry-backend, or other specific consuming applications. Use generic “host application” language only.
38
+
39
+ Sync to henry when core changes:
40
+
41
+ - **`docs/architecture.md`** → henry **`docs/architecture.md`** (Henry preamble + synced body; adapt `docs/flows/…` links in henry to **`docs/core-product-docs.md`**)
42
+ - **`docs/domain-services-and-adapters.md`** → update henry host summary; core remains canonical
43
+ - **`docs/conventions.md`**, **`docs/architecture-decisions.md`** → synced copies in henry when they exist in core (adapt flow-doc links in henry)
44
+ - **`docs/deployment/`**, **`docs/media-upload-pipeline-design.md`** → sync when shared deploy/media docs change (adapt flow links in henry)
45
+
46
+ **Do not** mirror **`docs/flows/`** into henry-backend. Henry maintains **`docs/core-product-docs.md`** as a pointer/index to core product documentation.
47
+
48
+ Do not overwrite henry-only docs (e.g. **`docs/henry-users-architecture.md`**, calculator architecture, **`docs/core-product-docs.md`**, **`docs/flows/henry/`**).
49
+
50
+ ## User-triggered sync
51
+
52
+ Phrases like **"sync rules with henry"** mean: diff **`.cursor/rules/`** and **`.cursor/skills/`** between repos and port any missing or stale shared content.
@@ -36,17 +36,17 @@ After the user accepts database changes (or if there were none):
36
36
 
37
37
  After the user accepts the API layer (or if there was no API layer):
38
38
 
39
- - Implement the **Repository** for the domain layer.
39
+ - Implement the **Repository** for the domain layer (Postgres only).
40
40
  - Include the **domain model** if data is retrieved from the database.
41
- - Focus on how data is read from and written to the database.
41
+ - If the feature needs non-SQL I/O (filesystem, HTTP, SSH), add **ports** in the domain and **adapter implementations** under `core_framework/infrastructure/<domain>/` per `docs/domain-services-and-adapters.md`—not in the repository.
42
42
  - Wait for the user to review and accept before proceeding.
43
43
 
44
44
  ## 4. Service and Application Layer
45
45
 
46
- After the user accepts the repository:
46
+ After the user accepts the repository (and any ports/adapters):
47
47
 
48
- - Implement the **service** layer.
49
- - Implement the **application** layer.
48
+ - Implement **domain service(s)** (persistence and any port-backed collaborators); wire in `dependencies.py`.
49
+ - Implement the **application** layer (orchestration across domains and transports—no permanent home for raw I/O libraries).
50
50
  - Wire everything together (replace 501 with real logic).
51
51
  - Wait for the user to review and accept before proceeding.
52
52
 
@@ -14,16 +14,18 @@ Use this architecture split consistently:
14
14
  - `core_framework/api/...` -> HTTP routers/schemas/dependencies only
15
15
  - `core_framework/worker/...` -> schedules/tasks/job handlers only
16
16
  - `core_framework/application/...` -> transport-agnostic orchestration shared by API + worker
17
- - `core_framework/domains/...` -> core domain logic, models, repositories
17
+ - `core_framework/domains/...` -> core domain logic, models, repositories (and optional ports/adapters for non-SQL I/O — see `domain-services-and-adapters`)
18
18
 
19
19
  ## Rules
20
20
 
21
21
  - API layer must not contain business orchestration that is shared with workers.
22
22
  - Worker layer must not contain domain business rules; call application services only.
23
- - Worker code must not import **`core_framework.domains`** packages (no direct domain repository/service usage from workers).
23
+ - Worker code must not import **`core_framework.domains`** packages (no direct domain repository/service usage from workers), **except** narrow **housekeeping cron** handlers that call a configured domain service facade (e.g. **`schedule_sweep_stale_avatar_staging`** → **`media_deps.avatar_service`**). Do not add new worker→domain shortcuts without the same documented housekeeping rationale.
24
24
  - Application layer must not import FastAPI/Starlette HTTP types.
25
25
  - Domain layer must not import API, worker, or application modules.
26
26
  - Domain layer must not import global infrastructure except in `dependencies.py`.
27
+ - Repositories are Postgres-only; filesystem, HTTP, SSH, and similar I/O use domain ports/adapters (separate collaborators), not repository methods.
28
+ - Application orchestrates multi-step flows; do not fold shared API+worker orchestration into domain services to avoid “application” existing.
27
29
 
28
30
  ## Dependency Direction
29
31
 
@@ -0,0 +1,35 @@
1
+ ---
2
+ description: Use concise English sentences for structlog event strings
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Structured logging (structlog)
7
+
8
+ Use **structlog** (`get_logger`, `logger.info`, `logger.exception`, etc.). The **first positional argument** is the **event** string (JSON `"event"` in non-local environments).
9
+
10
+ ## Event strings
11
+
12
+ - Write the event as a **short English sentence** — readable in local console and in log viewers.
13
+ - Use **sentence case**. Prefer past tense for failures (**"Failed to …"**, **"… failed"**).
14
+ - Put **IDs, counts, and context** in **keyword fields**, not in the event string.
15
+ - Do **not** use snake_case machine identifiers as events (for example `process_media_asset_variants_failed`).
16
+
17
+ ```python
18
+ # ✅ Preferred
19
+ logger.info(
20
+ "Skipped variant processing: staging registry missing",
21
+ owner_user_id=owner_user_id,
22
+ asset_id=asset_id,
23
+ )
24
+ logger.exception("Avatar variant processing failed", asset_id=asset_id)
25
+
26
+ # ❌ Avoid
27
+ logger.info("process_media_asset_variants_skipped_missing_registry", asset_id=asset_id)
28
+ logger.exception("delete_superseded_avatar_finals_failed", asset_id=asset_id)
29
+ ```
30
+
31
+ ## When editing
32
+
33
+ - **New** log calls must use English event strings.
34
+ - When you **change** logging in a function or module, convert nearby snake_case events in that same touch to English.
35
+ - Do **not** repo-wide migrate unrelated legacy event names unless the task asks for it.
@@ -111,6 +111,8 @@ A single `class <Domain>Service:` whose **`__init__`** takes `<Domain>Repository
111
111
 
112
112
  When real behavior is added later, apply `repository-read-consistency` (`/.cursor/rules/repository-read-consistency.mdc`) for reads and `_strong` naming.
113
113
 
114
+ If the bounded context needs filesystem, HTTP, or SSH, add **ports/adapters** or extra domain collaborators per `docs/domain-services-and-adapters.md` — do not put that I/O in `repository.py`.
115
+
114
116
  ### `__init__.py`
115
117
 
116
118
  Export the **classes** other layers may import: base exception, `<Domain>Repository`, `<Domain>Service`. List them in `__all__` per `domain-imports`. Do **not** re-export module-level `*_repository` / `*_service` instances from `dependencies` here.
@@ -7,6 +7,14 @@ description: Review code for quality, architecture compliance, and best practice
7
7
 
8
8
  When performing a code review, follow this workflow.
9
9
 
10
+ ## Output format (required)
11
+
12
+ Apply **`.cursor/rules/code-review-output.mdc`**. The user wants **actionable findings only**:
13
+
14
+ - Report bugs, wrong behavior, improvements, optimizations, and doc/code contradictions.
15
+ - **Do not** include "what's solid", praise sections, or positive overall assessments.
16
+ - If nothing is wrong, say so in one sentence—no filler.
17
+
10
18
  ## How to Ask (for the user)
11
19
 
12
20
  **Phrases that trigger this skill:** "review my code", "audit", "comprehensive review", "review comprehensively"
@@ -28,9 +36,10 @@ When performing a code review, follow this workflow.
28
36
  Read these files **before** proceeding with the review:
29
37
 
30
38
  1. **`docs/architecture.md`** – Domain boundaries, prohibited practices, and dependency rules
39
+ 1. **`docs/domain-services-and-adapters.md`** – Application orchestration vs domain services, ports/adapters, multiple collaborators per bounded context
31
40
  1. **`docs/conventions.md`** – Coding conventions, domain rules, caching, Redis, exception handling, API layer rules
32
41
  1. **`docs/architecture-decisions.md`** – Intentional design choices; do not flag these as issues
33
- 1. **`.cursor/rules/api-layer.mdc`**, **`.cursor/rules/application-layer.mdc`**, **`.cursor/rules/domain-imports.mdc`**, **`.cursor/rules/domain-caller-context.mdc`** – Apply when reviewing API/application/domain code
42
+ 1. **`.cursor/rules/api-layer.mdc`**, **`.cursor/rules/application-layer.mdc`**, **`.cursor/rules/domain-services-and-adapters.mdc`**, **`.cursor/rules/domain-imports.mdc`**, **`.cursor/rules/domain-caller-context.mdc`** – Apply when reviewing API/application/domain code
34
43
 
35
44
  ## Step 2: Key Context
36
45
 
@@ -41,8 +41,11 @@ jobs:
41
41
  ALLOWED_HOSTS: ${{ secrets.ALLOWED_HOSTS }}
42
42
  AVATAR_HOST_DOMAIN: ${{ secrets.AVATAR_HOST_DOMAIN }}
43
43
  AVATAR_DEFAULT_IMAGE: ${{ secrets.AVATAR_DEFAULT_IMAGE }}
44
- BANNER_BASE_URL: ${{ secrets.BANNER_BASE_URL }}
45
- BANNER_DEFAULT_URL: ${{ secrets.BANNER_DEFAULT_URL }}
44
+ BANNER_HOST_DOMAIN: ${{ secrets.BANNER_HOST_DOMAIN }}
45
+ BANNER_DEFAULT_IMAGE: ${{ secrets.BANNER_DEFAULT_IMAGE }}
46
+ IMAGE_SERVER_HOST: ${{ secrets.IMAGE_SERVER_HOST }}
47
+ IMAGE_SERVER_PORT: ${{ secrets.IMAGE_SERVER_PORT }}
48
+ IMAGE_SERVER_USER: ${{ secrets.IMAGE_SERVER_USER }}
46
49
  LOGFIRE_TOKEN: ${{ secrets.LOGFIRE_TOKEN }}
47
50
  CORS_ALLOWED_ORIGINS: ${{ secrets.CORS_ALLOWED_ORIGINS }}
48
51
  run: |
@@ -86,6 +89,28 @@ jobs:
86
89
  sys.exit(1)
87
90
  PY
88
91
 
92
+ - name: Generate image_server_ssh_key from secret
93
+ env:
94
+ IMAGE_SERVER_SSH_PRIVATE_KEY: ${{ secrets.IMAGE_SERVER_SSH_PRIVATE_KEY }}
95
+ run: |
96
+ set -euo pipefail
97
+
98
+ echo "Generating image_server_ssh_key..."
99
+ printf '%s\n' "$IMAGE_SERVER_SSH_PRIVATE_KEY" > image_server_ssh_key
100
+ test -s image_server_ssh_key
101
+ chmod 600 image_server_ssh_key
102
+
103
+ - name: Generate image_server_known_hosts from secret
104
+ env:
105
+ IMAGE_SERVER_KNOWN_HOSTS: ${{ secrets.IMAGE_SERVER_KNOWN_HOSTS }}
106
+ run: |
107
+ set -euo pipefail
108
+
109
+ echo "Generating image_server_known_hosts..."
110
+ printf '%s\n' "$IMAGE_SERVER_KNOWN_HOSTS" > image_server_known_hosts
111
+ test -s image_server_known_hosts
112
+ chmod 644 image_server_known_hosts
113
+
89
114
  - name: Upload rendered config.toml, firebase_config.json and docker-compose.yml
90
115
  uses: appleboy/scp-action@v1
91
116
  with:
@@ -93,7 +118,7 @@ jobs:
93
118
  username: ${{ secrets.SERVER_USER }}
94
119
  key: ${{ secrets.SERVER_SSH_KEY }}
95
120
  port: ${{ secrets.PORT }}
96
- source: "config.toml,firebase_config.json,docker-compose.yml"
121
+ source: "config.toml,firebase_config.json,docker-compose.yml,image_server_ssh_key,image_server_known_hosts"
97
122
  target: "/home/${{ secrets.SERVER_USER }}/app_configs/core-framework"
98
123
 
99
124
  - name: Deploy to Server via SSH
@@ -113,13 +138,30 @@ jobs:
113
138
  DOCKERHUB_USERNAME="${{ inputs.dockerhub_username }}"
114
139
  CONFIG_PATH="/home/${{ secrets.SERVER_USER }}/app_configs/core-framework/config.toml"
115
140
  FIREBASE_CONFIG_PATH="/home/${{ secrets.SERVER_USER }}/app_configs/core-framework/firebase_config.json"
141
+ AVATAR_STAGING_PATH="/home/${{ secrets.SERVER_USER }}/app_configs/core-framework/avatar-staging"
142
+ IMAGE_SERVER_SSH_KEY_PATH="/home/${{ secrets.SERVER_USER }}/app_configs/core-framework/image_server_ssh_key"
143
+ IMAGE_SERVER_KNOWN_HOSTS_PATH="/home/${{ secrets.SERVER_USER }}/app_configs/core-framework/image_server_known_hosts"
116
144
 
117
145
  echo "Pulling latest image"
118
146
  DOCKERHUB_USERNAME="$DOCKERHUB_USERNAME" \
119
147
  CONFIG_PATH="$CONFIG_PATH" \
120
148
  FIREBASE_CONFIG_PATH="$FIREBASE_CONFIG_PATH" \
149
+ AVATAR_STAGING_PATH="$AVATAR_STAGING_PATH" \
150
+ IMAGE_SERVER_SSH_KEY_PATH="$IMAGE_SERVER_SSH_KEY_PATH" \
151
+ IMAGE_SERVER_KNOWN_HOSTS_PATH="$IMAGE_SERVER_KNOWN_HOSTS_PATH" \
121
152
  docker compose -f "$COMPOSE_FILE" pull
122
153
 
154
+ echo "Preparing avatar staging directory and image server SSH key permissions"
155
+ mkdir -p "$AVATAR_STAGING_PATH"
156
+ docker run --rm \
157
+ --user root \
158
+ -v "$AVATAR_STAGING_PATH:/app/avatar-staging" \
159
+ -v "$IMAGE_SERVER_SSH_KEY_PATH:/app/image-server-ssh-key" \
160
+ -v "$IMAGE_SERVER_KNOWN_HOSTS_PATH:/app/image-server-known-hosts" \
161
+ --entrypoint "" \
162
+ "$DOCKERHUB_USERNAME/core-framework:latest" \
163
+ sh -c 'mkdir -p /app/avatar-staging && chown -R appuser:appgroup /app/avatar-staging && chown appuser:appgroup /app/image-server-ssh-key && chmod 600 /app/image-server-ssh-key && chown appuser:appgroup /app/image-server-known-hosts && chmod 644 /app/image-server-known-hosts'
164
+
123
165
  echo "Running database migrations (cf-alembic iterates core_framework.bundled_alembic.ALEMBIC_DOMAINS in order)"
124
166
  docker run --rm \
125
167
  --network host \
@@ -131,6 +173,9 @@ jobs:
131
173
  DOCKERHUB_USERNAME="$DOCKERHUB_USERNAME" \
132
174
  CONFIG_PATH="$CONFIG_PATH" \
133
175
  FIREBASE_CONFIG_PATH="$FIREBASE_CONFIG_PATH" \
176
+ AVATAR_STAGING_PATH="$AVATAR_STAGING_PATH" \
177
+ IMAGE_SERVER_SSH_KEY_PATH="$IMAGE_SERVER_SSH_KEY_PATH" \
178
+ IMAGE_SERVER_KNOWN_HOSTS_PATH="$IMAGE_SERVER_KNOWN_HOSTS_PATH" \
134
179
  docker compose -f "$COMPOSE_FILE" up -d \
135
180
  --remove-orphans \
136
181
  --force-recreate \
@@ -10,6 +10,9 @@ wheels/
10
10
  .venv
11
11
  .env
12
12
 
13
+ # Avatar ingest staging (local disk)
14
+ avatar-staging/
15
+
13
16
  # Logs
14
17
  logs/
15
18
 
@@ -21,6 +24,11 @@ htmlcov/
21
24
  # Firebase
22
25
  firebase_config.json
23
26
 
27
+ # Image server SSH (local dev; production via deploy secrets)
28
+ image-server-known-hosts
29
+ image_server_known_hosts
30
+ image_server_ssh_key
31
+
24
32
  # Cursor debug
25
33
  firebase-debug.log
26
34
  ui-debug.log
@@ -4,6 +4,39 @@ Notable changes to **core-framework** (import **`core_framework`**). Format foll
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.5.0] - 2026-05-26
8
+
9
+ ### Added
10
+
11
+ - **Avatar upload (v1):** **`POST /users/me/profile/avatar`**, **`media`** schema, worker variant processing, staging sweep cron, **`[avatar].staging_root`** config. See **`docs/flows/media/upload_pipeline.md`** and **`docs/flows/users/profile.md`**.
12
+
13
+ ### Fixed
14
+
15
+ - **Stale staging sweep:** **`delete_avatar_staging_blobs`** returns removed filenames; registry rows are deleted only when the staging file was removed or already missing (failed disk deletes retain the registry row for retry).
16
+ - **ARQ queue mismatch:** API **`RedisQueue`** pool now uses **`default_queue_name=core_framework:queue`**, matching worker consumers, so application enqueues (for example avatar variant processing) are no longer dropped on ARQ's default **`arq:queue`**.
17
+
18
+ ### Changed
19
+
20
+ - **Breaking:** **`[image_server].known_hosts_path`** is required; **`SshAvatarFinalsPublisherAdapter`** verifies the image server SSH host key (no longer **`known_hosts=None`**). Deploy must provide **`IMAGE_SERVER_KNOWN_HOSTS`** and mount **`/app/image-server-known-hosts`** on the worker. See **`docs/deployment/image-server-ssh.md`**.
21
+ - **Breaking:** **`[avatar].avatar_host_domain`** is **host-only** in **`config.toml`** and deploy secret **`AVATAR_HOST_DOMAIN`** (for example **`avatar.example.com`**, no **`https://`**). The application prepends **`https://`** when assembling public avatar URLs. Values that still include a scheme are normalized at load time.
22
+ - **Breaking:** **`[banner]`** config aligns with **`[avatar]`**: **`banner_host_domain`** + **`default_banner_image`** (deploy secrets **`BANNER_HOST_DOMAIN`**, **`BANNER_DEFAULT_IMAGE`**) replace **`base_url`** / **`default_url`**. Hostname is host-only; public banner URLs derive **`https://`** in application code.
23
+
24
+ ### Database
25
+
26
+ - **Media** baseline **`v1_media_init`**: **`media`** schema with **`avatar_staging_registry`** and **`avatar_ingest_lease`**. Hosts must set **`schema_media`** under **`[postgres_schemas]`**.
27
+ - **User** migration **`user_add_avatar_ingest_sequences`**: **`avatar_ingest_sequence`** and **`avatar_applied_sequence`** on **`user_profiles`**.
28
+ - Run **`uv run cf-alembic`** (or the host **`cf-alembic`** step) before deploying code that uses avatar upload or variant processing.
29
+
30
+ ### Documentation
31
+
32
+ - **Avatar upload edge limits:** **`docs/deployment/edge-upload-limits.md`**, **`docs/deployment/Caddyfile.example`** — route-scoped reverse-proxy **`max_size`** (**6 MiB** recommended) for **`POST /users/me/profile/avatar`**; updated **`docs/flows/media/upload_pipeline.md`** and **`docs/flows/users/profile.md`**.
33
+ - **Image server SSH:** **`docs/deployment/image-server-ssh.md`**, **`docs/deployment/image-server-known-hosts.example`** — production checklist for **`known_hosts_path`** and **`IMAGE_SERVER_KNOWN_HOSTS`**.
34
+ - **`docs/flows/media/upload_pipeline.md`**: status and HTTP surface aligned with shipped v1 implementation.
35
+ - **`docs/flows/users/profile.md`**: **`POST /users/me/profile/avatar`** flow; **`PATCH`** no longer documents **`avatar_id`** as planned.
36
+ - **`docs/flows/users/avatar.md`**, **`docs/flows/users/change_history.md`**: avatar change path and ingest MIME notes updated.
37
+ - **`docs/flows/users/avatar.md`**, **`docs/flows/users/profile.md`**: **`avatar_host_domain`** documented as host-only; public URLs derive **`https://`** in application code.
38
+ - **`docs/domain-services-and-adapters.md`**: **`banner_host_domain`** / **`default_banner_image`** config shape documented alongside avatar.
39
+
7
40
  ## [1.4.0] - 2026-05-19
8
41
 
9
42
  ### Changed
@@ -24,7 +57,7 @@ Notable changes to **core-framework** (import **`core_framework`**). Format foll
24
57
  ### Documentation
25
58
 
26
59
  - **`docs/flows/users/avatar.md`**: multi-size **`avatar`** response shape and CDN URL convention.
27
- - **`docs/flows/media/upload_pipeline.md`**: upload pipeline design (implementation pending).
60
+ - **`docs/flows/media/upload_pipeline.md`**: upload pipeline design and v1 implementation reference.
28
61
  - **`docs/flows/users/profile.md`**: DOB on get/update flows and validation errors.
29
62
  - **`docs/flows/users/change_history.md`**: profile **`date_of_birth`** changes are not written to change history (audit trigger unchanged).
30
63
  - **`docs/patch-update-typed-payload.md`**: typed PATCH decisions and patterns for partial-update endpoints.
@@ -170,4 +203,5 @@ First **SemVer-stable** release per **`docs/package-api.md`**.
170
203
  [1.2.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.1.1...v1.2.0
171
204
  [1.3.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.2.0...v1.3.0
172
205
  [1.4.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.3.0...v1.4.0
173
- [unreleased]: https://github.com/NepNepFFXIV/core-framework/compare/v1.4.0...HEAD
206
+ [1.5.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.4.0...v1.5.0
207
+ [unreleased]: https://github.com/NepNepFFXIV/core-framework/compare/v1.5.0...HEAD
@@ -1,17 +1,19 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: core-framework
3
- Version: 1.4.0
3
+ Version: 1.5.0
4
4
  Summary: Core framework package (import as core_framework)
5
5
  Project-URL: Homepage, https://github.com/NepNepFFXIV/core-framework
6
6
  Project-URL: Repository, https://github.com/NepNepFFXIV/core-framework
7
7
  License-Expression: MIT
8
8
  License-File: LICENSE
9
9
  Requires-Python: >=3.14
10
+ Requires-Dist: aiofiles>=25.1.0
10
11
  Requires-Dist: alembic>=1.18.4
11
12
  Requires-Dist: arq>=0.28.0
12
13
  Requires-Dist: async-lru>=2.1.0
13
14
  Requires-Dist: asyncer>=0.0.14
14
15
  Requires-Dist: asyncpg>=0.31.0
16
+ Requires-Dist: asyncssh>=2.23.0
15
17
  Requires-Dist: brotli-asgi>=1.6.0
16
18
  Requires-Dist: cashews[redis]>=7.5.0
17
19
  Requires-Dist: device-detector>=6.2.0
@@ -22,6 +24,7 @@ Requires-Dist: itsdangerous>=2.2.0
22
24
  Requires-Dist: logfire[asyncpg,fastapi,httpx,redis]>=4.32.1
23
25
  Requires-Dist: mashumaro[orjson]>=3.20
24
26
  Requires-Dist: orjson>=3.11.7
27
+ Requires-Dist: pillow>=12.2.0
25
28
  Requires-Dist: python-ulid>=3.1.0
26
29
  Requires-Dist: structlog>=25.5.0
27
30
  Requires-Dist: tenacity>=9.1.4
@@ -0,0 +1,72 @@
1
+ import asyncio
2
+ from logging.config import fileConfig
3
+
4
+ from sqlalchemy import pool, text
5
+ from sqlalchemy.engine import Connection
6
+ from sqlalchemy.ext.asyncio import async_engine_from_config
7
+
8
+ from alembic import context
9
+ from core_framework.core.settings import load_default_settings
10
+
11
+ settings = load_default_settings()
12
+
13
+ config = context.config
14
+
15
+ if config.config_file_name is not None:
16
+ fileConfig(config.config_file_name)
17
+
18
+ target_metadata = None
19
+
20
+ config.set_main_option("sqlalchemy.url", settings.write_postgres.alembic_postgres_url)
21
+
22
+
23
+ def run_migrations_offline() -> None:
24
+ url = config.get_main_option("sqlalchemy.url")
25
+ context.configure(
26
+ url=url,
27
+ target_metadata=target_metadata,
28
+ literal_binds=True,
29
+ dialect_opts={"paramstyle": "named"},
30
+ )
31
+
32
+ quoted_schema_name = f'"{settings.postgres_schemas.schema_media.replace('"', '""')}"'
33
+
34
+ with context.begin_transaction():
35
+ context.execute(text(f"set search_path to {quoted_schema_name}"))
36
+ context.run_migrations()
37
+
38
+
39
+ def do_run_migrations(connection: Connection) -> None:
40
+ quoted_schema_name = f'"{settings.postgres_schemas.schema_media.replace('"', '""')}"'
41
+
42
+ connection.execute(text(f"create schema if not exists {quoted_schema_name}"))
43
+ connection.execute(text(f"set search_path to {quoted_schema_name}"))
44
+ connection.commit()
45
+
46
+ context.configure(connection=connection, target_metadata=target_metadata)
47
+
48
+ with context.begin_transaction():
49
+ context.run_migrations()
50
+
51
+
52
+ async def run_async_migrations() -> None:
53
+ connectable = async_engine_from_config(
54
+ config.get_section(config.config_ini_section, {}),
55
+ prefix="sqlalchemy.",
56
+ poolclass=pool.NullPool,
57
+ )
58
+
59
+ async with connectable.connect() as connection:
60
+ await connection.run_sync(do_run_migrations)
61
+
62
+ await connectable.dispose()
63
+
64
+
65
+ def run_migrations_online() -> None:
66
+ asyncio.run(run_async_migrations())
67
+
68
+
69
+ if context.is_offline_mode():
70
+ run_migrations_offline()
71
+ else:
72
+ run_migrations_online()
@@ -0,0 +1,45 @@
1
+ """v1 media baseline
2
+
3
+ Initial migration for the media schema: avatar staging registry and ingest lease.
4
+
5
+ Revision ID: v1_media_init
6
+ Revises:
7
+ Create Date: 2026-05-20 12:00:00.000000
8
+
9
+ """
10
+
11
+ from collections.abc import Sequence
12
+
13
+ from alembic import op
14
+
15
+ revision: str = "v1_media_init"
16
+ down_revision: str | Sequence[str] | None = None
17
+ branch_labels: str | Sequence[str] | None = None
18
+ depends_on: str | Sequence[str] | None = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ op.execute("""
23
+ create table avatar_staging_registry (
24
+ asset_id varchar(26) not null primary key,
25
+ owner_user_id varchar(128) not null,
26
+ staging_filename text not null,
27
+ created_at timestamptz not null default now()
28
+ );
29
+ """)
30
+
31
+ op.execute("""
32
+ create index idx_avatar_staging_registry_created_at
33
+ on avatar_staging_registry (created_at);
34
+ """)
35
+
36
+ op.execute("""
37
+ create table avatar_ingest_lease (
38
+ owner_user_id varchar(128) not null primary key,
39
+ lease_expires_at timestamptz not null
40
+ );
41
+ """)
42
+
43
+
44
+ def downgrade() -> None:
45
+ raise RuntimeError("downgrade is not supported for v1 baseline migrations")