core-framework 1.8.0__tar.gz → 2.0.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.
- core_framework-2.0.0/.cursor/rules/alembic-revision-ids.mdc +39 -0
- core_framework-2.0.0/.cursor/rules/api-security.mdc +46 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/api-validation.mdc +5 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/application-layer.mdc +17 -4
- core_framework-2.0.0/.cursor/rules/client-error-visibility.mdc +75 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/code-review-output.mdc +1 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/domain-caller-context.mdc +2 -3
- core_framework-2.0.0/.cursor/rules/domain-imports.mdc +124 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/domain-input-guards.mdc +4 -4
- core_framework-2.0.0/.cursor/rules/exception-handlers.mdc +59 -0
- core_framework-2.0.0/.cursor/rules/inline-raise-messages.mdc +46 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/layer-boundaries.mdc +1 -0
- core_framework-2.0.0/.cursor/rules/repository-schema-isolation.mdc +72 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/skills/add-config/SKILL.md +9 -5
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/skills/add-domain/SKILL.md +1 -1
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/skills/code-review/SKILL.md +22 -2
- {core_framework-1.8.0 → core_framework-2.0.0}/.github/workflows/_deploy.yml +45 -36
- {core_framework-1.8.0 → core_framework-2.0.0}/.github/workflows/test.yaml +1 -1
- {core_framework-1.8.0 → core_framework-2.0.0}/CHANGELOG.md +84 -1
- {core_framework-1.8.0 → core_framework-2.0.0}/PKG-INFO +2 -1
- core_framework-2.0.0/alembic/comment/alembic/versions/comment_add_comment_attachments.py +37 -0
- core_framework-2.0.0/alembic/comment/alembic/versions/comment_add_comment_create_idempotency.py +44 -0
- core_framework-2.0.0/alembic/comment/alembic/versions/comment_tombstone_status_model.py +103 -0
- core_framework-2.0.0/alembic/media/alembic/versions/media_add_attachment_staging_registry.py +44 -0
- core_framework-2.0.0/alembic/media/alembic/versions/media_attachment_staging_registry_published_at.py +34 -0
- core_framework-2.0.0/alembic/post/alembic/versions/post_add_post_attachments.py +37 -0
- core_framework-2.0.0/alembic/post/alembic/versions/post_add_post_create_idempotency.py +41 -0
- core_framework-2.0.0/alembic/post/alembic/versions/post_tombstone_status_model.py +95 -0
- core_framework-2.0.0/alembic/user/alembic/versions/user_reserve_sentinel_user_ids.py +34 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/config.toml +13 -1
- {core_framework-1.8.0 → core_framework-2.0.0}/config.toml.template +17 -5
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/admin/cache/schemas.py +4 -8
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/admin/comments/router.py +24 -22
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/admin/comments/schemas.py +2 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/admin/moderation/router.py +10 -2
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/admin/posts/router.py +19 -14
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/admin/posts/schemas.py +2 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/admin/users/router.py +16 -4
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/admin/users/schemas.py +5 -0
- core_framework-2.0.0/core_framework/api/attachments/router.py +51 -0
- core_framework-2.0.0/core_framework/api/attachments/schemas.py +51 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/comments/authenticated/mappers.py +8 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/comments/authenticated/router.py +28 -11
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/comments/authenticated/schemas.py +28 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/comments/public/schemas.py +4 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/dependencies.py +4 -10
- core_framework-2.0.0/core_framework/api/idempotency/__init__.py +5 -0
- core_framework-2.0.0/core_framework/api/idempotency/dependencies.py +56 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/posts/authenticated/mappers.py +8 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/posts/authenticated/router.py +11 -1
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/posts/authenticated/schemas.py +23 -2
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/posts/public/schemas.py +4 -1
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/router.py +2 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/system/router.py +3 -2
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/users/authenticated/router.py +32 -22
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/users/shared/schemas.py +17 -55
- core_framework-2.0.0/core_framework/application/auth/auth_service.py +24 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/comments/admin_service.py +103 -44
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/comments/aggregation_service.py +8 -0
- core_framework-2.0.0/core_framework/application/comments/authenticated_service.py +220 -0
- core_framework-2.0.0/core_framework/application/comments/destructive_delete_cleanup.py +66 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/comments/public_service.py +47 -25
- core_framework-2.0.0/core_framework/application/comments/tombstone_cleanup.py +62 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/events/event_token.py +1 -2
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/media/README.md +1 -1
- core_framework-2.0.0/core_framework/application/media/attachment_read.py +77 -0
- core_framework-2.0.0/core_framework/application/media/attachment_service.py +490 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/media/avatar_service.py +35 -10
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/media/banner_service.py +29 -10
- core_framework-2.0.0/core_framework/application/media/ingest_guards.py +17 -0
- core_framework-2.0.0/core_framework/application/media/read_url_config.py +50 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/moderation/appeal_service.py +19 -18
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/moderation/user_service.py +1 -1
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/notifications/inbox_service.py +4 -6
- core_framework-2.0.0/core_framework/application/notifications/mute_service.py +38 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/notifications/notification_service.py +8 -8
- core_framework-2.0.0/core_framework/application/posts/admin_service.py +180 -0
- core_framework-2.0.0/core_framework/application/posts/authenticated_service.py +117 -0
- core_framework-2.0.0/core_framework/application/posts/destructive_delete_cleanup.py +82 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/posts/public_service.py +54 -14
- core_framework-2.0.0/core_framework/application/posts/tombstone_cleanup.py +64 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/shared/enums.py +6 -0
- core_framework-2.0.0/core_framework/application/shared/exceptions.py +52 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/users/admin_service.py +35 -42
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/users/authenticated_service.py +58 -8
- core_framework-2.0.0/core_framework/application/users/enums.py +6 -0
- core_framework-2.0.0/core_framework/application/users/user_deletion_service.py +251 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/bundled_alembic.py +2 -4
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/core/cache.py +15 -18
- core_framework-2.0.0/core_framework/core/exception_handlers/application.py +130 -0
- core_framework-2.0.0/core_framework/core/exception_handlers/common.py +69 -0
- core_framework-2.0.0/core_framework/core/exception_handlers/setup.py +36 -0
- core_framework-2.0.0/core_framework/core/middleware.py +116 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/core/pagination.py +2 -4
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/core/runtime.py +1 -2
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/core/settings.py +51 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/comment/README.md +24 -27
- core_framework-2.0.0/core_framework/domains/comment/__init__.py +51 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/comment/constants.py +1 -0
- core_framework-2.0.0/core_framework/domains/comment/enums.py +32 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/comment/exceptions.py +4 -5
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/comment/models.py +94 -1
- core_framework-2.0.0/core_framework/domains/comment/outcomes.py +19 -0
- core_framework-2.0.0/core_framework/domains/comment/repository.py +2378 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/comment/service.py +376 -92
- core_framework-2.0.0/core_framework/domains/exceptions.py +11 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/README.md +1 -1
- core_framework-2.0.0/core_framework/domains/media/__init__.py +59 -0
- core_framework-2.0.0/core_framework/domains/media/attachment/components/finals_publisher.py +45 -0
- core_framework-2.0.0/core_framework/domains/media/attachment/components/staging.py +164 -0
- core_framework-2.0.0/core_framework/domains/media/attachment/components/variant_encoder.py +53 -0
- core_framework-2.0.0/core_framework/domains/media/attachment/constants.py +41 -0
- core_framework-2.0.0/core_framework/domains/media/attachment/exceptions.py +25 -0
- core_framework-2.0.0/core_framework/domains/media/attachment/models.py +27 -0
- core_framework-2.0.0/core_framework/domains/media/attachment/ports/finals_publisher.py +36 -0
- core_framework-2.0.0/core_framework/domains/media/attachment/ports/staging.py +44 -0
- core_framework-2.0.0/core_framework/domains/media/attachment/ports/variant_encoder.py +34 -0
- core_framework-2.0.0/core_framework/domains/media/attachment/service.py +489 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/avatar/components/staging.py +20 -2
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/avatar/service.py +51 -1
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/banner/components/staging.py +20 -2
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/banner/service.py +51 -1
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/dependencies.py +93 -1
- core_framework-2.0.0/core_framework/domains/media/shared/exceptions.py +9 -0
- core_framework-2.0.0/core_framework/domains/media/shared/outcomes.py +21 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/shared/repository.py +298 -4
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/moderation/__init__.py +5 -13
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/moderation/enums.py +4 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/moderation/exceptions.py +4 -5
- core_framework-2.0.0/core_framework/domains/moderation/outcomes.py +18 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/moderation/repository.py +28 -15
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/moderation/service.py +97 -22
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/notification/README.md +11 -6
- core_framework-2.0.0/core_framework/domains/notification/exceptions.py +5 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/notification/repository.py +38 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/notification/service.py +14 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/post/README.md +31 -20
- core_framework-2.0.0/core_framework/domains/post/__init__.py +41 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/post/constants.py +1 -0
- core_framework-2.0.0/core_framework/domains/post/enums.py +40 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/post/exceptions.py +4 -5
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/post/models.py +64 -1
- core_framework-2.0.0/core_framework/domains/post/outcomes.py +7 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/post/repository.py +790 -153
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/post/service.py +282 -62
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/user/__init__.py +11 -11
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/user/constants.py +8 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/user/exceptions.py +9 -5
- core_framework-2.0.0/core_framework/domains/user/outcomes.py +24 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/user/repository.py +46 -16
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/user/service.py +60 -9
- core_framework-2.0.0/core_framework/domains/utils.py +236 -0
- core_framework-2.0.0/core_framework/infrastructure/media/attachment_staging_display_geometry.py +67 -0
- core_framework-2.0.0/core_framework/infrastructure/media/local_attachment_staging_adapter.py +73 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/infrastructure/media/local_banner_staging_adapter.py +3 -32
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/infrastructure/media/local_staging_adapter.py +3 -10
- core_framework-2.0.0/core_framework/infrastructure/media/pillow_attachment_variant_encoder_adapter.py +130 -0
- core_framework-2.0.0/core_framework/infrastructure/media/ssh_attachment_finals_publisher_adapter.py +130 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/infrastructure/media/ssh_finals_publisher_adapter.py +1 -1
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/main.py +2 -9
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/testing/arq.py +27 -1
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/testing/firebase.py +1 -2
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/testing/media.py +91 -5
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/schedules/__init__.py +10 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/schedules/schedule_expired_account_deletions.py +5 -0
- core_framework-2.0.0/core_framework/worker/schedules/schedule_sweep_stale_attachment_staging.py +25 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/tasks/__init__.py +12 -0
- core_framework-2.0.0/core_framework/worker/tasks/delete_attachment_finals.py +28 -0
- core_framework-2.0.0/core_framework/worker/tasks/process_account_deletion.py +21 -0
- core_framework-2.0.0/core_framework/worker/tasks/process_admin_user_deletion.py +34 -0
- core_framework-2.0.0/core_framework/worker/tasks/process_attachment_asset_variants.py +29 -0
- core_framework-2.0.0/core_framework/worker/tasks/process_destructive_comment_delete.py +15 -0
- core_framework-2.0.0/core_framework/worker/tasks/process_destructive_post_delete.py +15 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/tasks/process_media_asset_variants.py +6 -0
- core_framework-2.0.0/core_framework/worker/tasks/process_user_content_deletion.py +15 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/dockerfile +16 -2
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/deployments/README.md +13 -11
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/deployments/deploy-playbook.md +56 -19
- core_framework-2.0.0/docs/deployments/edge-upload-limits.md +67 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/deployments/examples/Caddyfile.example +12 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/deployments/guides/app-server-deploy-ssh.md +12 -7
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/deployments/guides/image-server-ssh.md +8 -3
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/deployments/guides/postgres-setup.md +14 -14
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/deployments/guides/redis-setup.md +5 -5
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/deployments/guides/ubuntu-server-setup.md +1 -1
- core_framework-2.0.0/docs/domains/README.md +17 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/comments/admin_comments.md +43 -71
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/comments/comment_stats_aggregation.md +9 -9
- core_framework-2.0.0/docs/domains/comments/create_comment.md +170 -0
- core_framework-2.0.0/docs/domains/comments/delete_comment.md +67 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/comments/edit_comment.md +4 -4
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/comments/retrieve_comments.md +28 -22
- core_framework-2.0.0/docs/domains/deletion/comments.md +288 -0
- core_framework-2.0.0/docs/domains/deletion/overview.md +185 -0
- core_framework-2.0.0/docs/domains/deletion/posts.md +298 -0
- core_framework-2.0.0/docs/domains/deletion/users.md +458 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/media/avatar-upload-design.md +1 -1
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/media/banner-upload-design.md +1 -1
- core_framework-2.0.0/docs/domains/media/post-comment-attachments-design.md +522 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/media/upload-pipeline-design.md +3 -3
- core_framework-2.0.0/docs/domains/media/upload_pipeline.md +91 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/mentions/mentions_in_content.md +2 -2
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/moderation/appeals.md +4 -3
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/moderation/internal_notes.md +1 -1
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/notifications/notification_inbox.md +28 -17
- core_framework-2.0.0/docs/domains/posts/admin_posts.md +212 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/posts/author_context.md +11 -5
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/posts/hashtag_discovery.md +1 -1
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/posts/post_like.md +1 -1
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/posts/post_stats_aggregation.md +19 -10
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/posts/post_visibility.md +14 -6
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/users/account_deletion.md +10 -8
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/users/blocks.md +1 -1
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/users/follow-system-design.md +2 -2
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/users/follow.md +3 -3
- core_framework-2.0.0/docs/domains/users/user_removal.md +129 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/library/api.md +16 -9
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/platform/README.md +12 -9
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/platform/architecture-decisions.md +52 -14
- core_framework-2.0.0/docs/platform/client-error-visibility.md +228 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/platform/conventions.md +11 -6
- core_framework-2.0.0/docs/platform/create-idempotency-design.md +273 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/platform/domain-services-and-adapters.md +1 -0
- core_framework-2.0.0/docs/platform/media-storage-topology.md +141 -0
- core_framework-2.0.0/docs/todo.md +12 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/makefile +18 -8
- {core_framework-1.8.0 → core_framework-2.0.0}/pyproject.toml +6 -1
- core_framework-2.0.0/tests/integration/_http_error_details.py +19 -0
- core_framework-2.0.0/tests/integration/api/_destructive_delete_helpers.py +51 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/_http_helpers.py +20 -4
- core_framework-2.0.0/tests/integration/api/_user_deletion_helpers.py +63 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/admin/cache/router_test.py +3 -2
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/admin/comments/router_test.py +216 -91
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/admin/moderation/router_test.py +33 -8
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/admin/posts/router_test.py +51 -82
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/admin/users/router_test.py +33 -171
- core_framework-2.0.0/tests/integration/api/attachments/_helpers.py +214 -0
- core_framework-2.0.0/tests/integration/api/attachments/attachment_upload_test.py +122 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/comments/authenticated/comment_writes_integration_test.py +180 -2
- core_framework-2.0.0/tests/integration/api/comments/comment_attachments_integration_test.py +280 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/comments/public/router_test.py +126 -9
- core_framework-2.0.0/tests/integration/api/create_idempotency_integration_test.py +481 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/posts/authenticated/post_writes_integration_test.py +3 -1
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/posts/comment_count_aggregation_test.py +12 -12
- core_framework-2.0.0/tests/integration/api/posts/post_attachments_integration_test.py +383 -0
- core_framework-2.0.0/tests/integration/api/posts/post_deletion_integration_test.py +547 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/posts/public/router_test.py +19 -9
- core_framework-2.0.0/tests/integration/api/public_read_by_id_unavailable_detail_test.py +31 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/users/authenticated/router_test.py +19 -9
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/users/public/router_test.py +2 -1
- core_framework-2.0.0/tests/integration/api/users/user_deletion_integration_test.py +245 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/worker/account_deletion_test.py +52 -10
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/worker/aggregate_comment_stats_test.py +70 -1
- core_framework-2.0.0/tests/integration/worker/attachment_upload_test.py +96 -0
- core_framework-2.0.0/tests/integration/worker/utils_test.py +124 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/unit/application/notifications/inbox_service_test.py +2 -2
- core_framework-2.0.0/tests/unit/infrastructure/media/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/unit/infrastructure/media/ssh_finals_publisher_adapter_test.py +2 -3
- {core_framework-1.8.0 → core_framework-2.0.0}/uv.lock +43 -1
- core_framework-1.8.0/.cursor/rules/api-security.mdc +0 -18
- core_framework-1.8.0/.cursor/rules/domain-imports.mdc +0 -99
- core_framework-1.8.0/.cursor/rules/exception-handlers.mdc +0 -33
- core_framework-1.8.0/core_framework/application/auth/auth_service.py +0 -16
- core_framework-1.8.0/core_framework/application/comments/authenticated_service.py +0 -142
- core_framework-1.8.0/core_framework/application/notifications/mute_service.py +0 -18
- core_framework-1.8.0/core_framework/application/posts/admin_service.py +0 -131
- core_framework-1.8.0/core_framework/application/posts/authenticated_service.py +0 -90
- core_framework-1.8.0/core_framework/application/shared/exceptions.py +0 -16
- core_framework-1.8.0/core_framework/core/exception_handlers/comment.py +0 -84
- core_framework-1.8.0/core_framework/core/exception_handlers/common.py +0 -19
- core_framework-1.8.0/core_framework/core/exception_handlers/media.py +0 -104
- core_framework-1.8.0/core_framework/core/exception_handlers/moderation.py +0 -84
- core_framework-1.8.0/core_framework/core/exception_handlers/notification.py +0 -22
- core_framework-1.8.0/core_framework/core/exception_handlers/post.py +0 -48
- core_framework-1.8.0/core_framework/core/exception_handlers/setup.py +0 -82
- core_framework-1.8.0/core_framework/core/exception_handlers/user.py +0 -60
- core_framework-1.8.0/core_framework/core/middleware.py +0 -64
- core_framework-1.8.0/core_framework/domains/comment/__init__.py +0 -34
- core_framework-1.8.0/core_framework/domains/comment/enums.py +0 -10
- core_framework-1.8.0/core_framework/domains/comment/repository.py +0 -1451
- core_framework-1.8.0/core_framework/domains/exceptions.py +0 -6
- core_framework-1.8.0/core_framework/domains/media/__init__.py +0 -59
- core_framework-1.8.0/core_framework/domains/media/shared/exceptions.py +0 -10
- core_framework-1.8.0/core_framework/domains/notification/exceptions.py +0 -6
- core_framework-1.8.0/core_framework/domains/post/__init__.py +0 -23
- core_framework-1.8.0/core_framework/domains/post/enums.py +0 -18
- core_framework-1.8.0/core_framework/domains/utils.py +0 -121
- core_framework-1.8.0/core_framework/worker/tasks/process_account_deletion.py +0 -29
- core_framework-1.8.0/docs/deployments/edge-upload-limits.md +0 -61
- core_framework-1.8.0/docs/domains/README.md +0 -16
- core_framework-1.8.0/docs/domains/comments/create_comment.md +0 -135
- core_framework-1.8.0/docs/domains/comments/delete_comment.md +0 -60
- core_framework-1.8.0/docs/domains/media/upload_pipeline.md +0 -60
- core_framework-1.8.0/docs/domains/posts/admin_posts.md +0 -247
- core_framework-1.8.0/docs/domains/users/user_removal.md +0 -86
- core_framework-1.8.0/docs/todo.md +0 -11
- core_framework-1.8.0/tests/integration/worker/utils_test.py +0 -46
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/api-layer.mdc +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/api-reference-docs.mdc +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/constants-final.mdc +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/database-triggers.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/docstrings.mdc +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/domain-repository-exceptions.mdc +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/domain-services-and-adapters.mdc +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/flow-documentation.mdc +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/henry-cursor-rules-sync.mdc +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/implementation-workflow.mdc +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/integration-test-strategy.mdc +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/karpathy-guidelines.mdc +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/no-code-in-docs.mdc +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/postgres-config-conventions.mdc +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/repository-read-consistency.mdc +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/strong-read-opt-in.mdc +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/structured-logging.mdc +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/rules/tech-stack.mdc +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.cursor/skills/recommend-features/SKILL.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.dockerignore +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.github/workflows/dev-ci-cd.yaml +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.github/workflows/manual-deployment.yaml +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.github/workflows/publish-pypi.yml +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.gitignore +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.pre-commit-config.yaml +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/.python-version +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/LICENSE +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/README.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/comment/alembic/README +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/comment/alembic/env.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/comment/alembic/script.py.mako +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/comment/alembic/versions/comment_add_comment_mentions.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/comment/alembic/versions/v1_comment_init_baseline.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/comment/alembic.ini +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/extension/alembic/README +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/extension/alembic/env.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/extension/alembic/script.py.mako +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/extension/alembic/versions/v1_ext_init_baseline.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/extension/alembic.ini +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/media/alembic/README +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/media/alembic/env.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/media/alembic/script.py.mako +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/media/alembic/versions/media_add_banner_staging_and_lease.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/media/alembic/versions/v1_media_init_baseline.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/media/alembic.ini +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/moderation/alembic/README +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/moderation/alembic/env.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/moderation/alembic/script.py.mako +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/moderation/alembic/versions/v1_mod_init_baseline.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/moderation/alembic.ini +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/notification/alembic/README +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/notification/alembic/env.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/notification/alembic/script.py.mako +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/notification/alembic/versions/v1_notif_init_baseline.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/notification/alembic.ini +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/post/alembic/README +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/post/alembic/env.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/post/alembic/script.py.mako +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/post/alembic/versions/post_add_post_mentions.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/post/alembic/versions/v1_post_init_baseline.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/post/alembic.ini +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/user/alembic/README +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/user/alembic/env.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/user/alembic/script.py.mako +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/user/alembic/versions/user_add_avatar_ingest_sequences.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/user/alembic/versions/user_add_banner_ingest_sequences.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/user/alembic/versions/user_add_date_of_birth_to_profiles.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/user/alembic/versions/v1_user_init_baseline.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/alembic/user/alembic.ini +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/admin/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/admin/cache/router.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/admin/moderation/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/admin/moderation/schemas.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/admin/router.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/admin/users/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/auth/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/auth/router.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/auth/schemas.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/comments/public/router.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/comments/router.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/comments/schemas.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/constants.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/events/router.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/events/schemas.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/notifications/authenticated/router.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/notifications/authenticated/schemas.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/notifications/router.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/posts/public/router.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/posts/router.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/posts/schemas.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/schemas.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/system/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/users/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/users/authenticated/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/users/authenticated/mappers.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/users/authenticated/schemas.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/users/mappers.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/users/public/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/users/public/router.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/users/public/schemas.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/api/users/router.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/auth/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/auth/access_service.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/auth/models.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/bootstrap.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/cache/invalidate_service.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/cache/models.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/events/README.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/events/event_service.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/events/models.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/moderation/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/moderation/moderator_service.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/moderation/report_service.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/moderation/scheduled_service.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/notifications/README.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/notifications/enums.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/posts/aggregation_service.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/shared/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/shared/user_agent.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/shared/worker_jobs.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/users/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/users/aggregation_service.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/users/public_service.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/application/users/scheduled_service.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/asgi.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/constants.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/core/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/core/context.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/core/database.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/core/exception_handlers/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/core/firebase.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/core/http_client.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/core/logging.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/core/observability.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/core/redis.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/comment/dependencies.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/avatar/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/avatar/components/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/avatar/components/finals_publisher.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/avatar/components/variant_encoder.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/avatar/constants.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/avatar/exceptions.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/avatar/models.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/avatar/ports/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/avatar/ports/finals_publisher.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/avatar/ports/staging.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/avatar/ports/variant_encoder.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/banner/components/finals_publisher.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/banner/components/variant_encoder.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/banner/constants.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/banner/exceptions.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/banner/models.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/banner/ports/finals_publisher.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/banner/ports/staging.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/banner/ports/variant_encoder.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/shared/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/shared/constants.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/shared/service.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/media/shared/utils.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/moderation/README.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/moderation/dependencies.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/moderation/models.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/notification/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/notification/dependencies.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/notification/enums.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/notification/models.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/post/dependencies.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/user/README.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/user/dependencies.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/user/enums.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/user/models.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/domains/user/utils.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/infrastructure/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/infrastructure/media/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/infrastructure/media/pillow_avatar_variant_encoder_adapter.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/infrastructure/media/pillow_banner_variant_encoder_adapter.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/infrastructure/media/pillow_staging_image.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/infrastructure/media/ssh_banner_finals_publisher_adapter.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/migrate_cli.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/testing/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/testing/auth.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/testing/config.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/testing/containers.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/testing/hookspecs.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/testing/httpx_test_client.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/testing/migrations.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/testing/plugin.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/main.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/schedules/schedule_aggregate_comment_stats.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/schedules/schedule_aggregate_post_stats.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/schedules/schedule_aggregate_user_stats.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/schedules/schedule_expired_mute_lifts.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/schedules/schedule_sweep_stale_avatar_staging.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/schedules/schedule_sweep_stale_banner_staging.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/tasks/delete_superseded_avatar_finals.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/tasks/delete_superseded_banner_finals.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/tasks/process_aggregate_comment_stats.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/tasks/process_aggregate_post_stats.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/tasks/process_aggregate_user_stats.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/tasks/process_banner_asset_variants.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/tasks/process_cache_pattern_invalidation.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/tasks/process_mute_lift.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/core_framework/worker/worker_context.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docker-compose.dev.yaml +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docker-compose.yml +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/README.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/deployments/examples/image-server-known-hosts.example +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/deployments/release-playbook.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/auth/access_control.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/auth/registration.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/cache/admin_cache_invalidation.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/comments/comment_report.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/events/events.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/moderation/moderator_actions.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/moderation/reports.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/moderation/restrictions.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/users/account.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/users/avatar.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/users/banner.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/users/change_history.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/users/check_username_exists.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/users/my_posts_and_comments.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/users/preferences.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/users/profile.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/domains/users/public_profile.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/library/README.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/library/core-framework-migration.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/library/overview.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/library/package-api.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/library/testing-plugin-design.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/platform/database-triggers.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/platform/event-outbox-design.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/platform/layers-and-boundaries.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/docs/platform/patch-update-typed-payload.md +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/firebase_config.example.json +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/conftest.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/admin/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/admin/cache/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/admin/comments/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/admin/moderation/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/admin/posts/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/admin/users/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/auth/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/auth/router_test.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/comments/__init__.py +0 -0
- {core_framework-1.8.0/tests/integration/api/comments/public → core_framework-2.0.0/tests/integration/api/comments/authenticated}/__init__.py +0 -0
- {core_framework-1.8.0/tests/integration/api/notifications → core_framework-2.0.0/tests/integration/api/comments/public}/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/events/router_test.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/mentions/mentions_integration_test.py +0 -0
- {core_framework-1.8.0/tests/integration/api/posts → core_framework-2.0.0/tests/integration/api/notifications}/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/notifications/router_test.py +0 -0
- {core_framework-1.8.0/tests/integration/api/posts/public → core_framework-2.0.0/tests/integration/api/posts}/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/posts/followers_visibility_test.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/posts/post_stats_dirty_marking_test.py +0 -0
- {core_framework-1.8.0/tests/integration/api/system → core_framework-2.0.0/tests/integration/api/posts/public}/__init__.py +0 -0
- {core_framework-1.8.0/tests/integration/api/users → core_framework-2.0.0/tests/integration/api/system}/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/system/router_test.py +0 -0
- {core_framework-1.8.0/tests/integration/api/users/authenticated → core_framework-2.0.0/tests/integration/api/users}/__init__.py +0 -0
- {core_framework-1.8.0/tests/integration/api/users/public → core_framework-2.0.0/tests/integration/api/users/authenticated}/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/users/authenticated/avatar_upload_test.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/api/users/authenticated/banner_upload_test.py +0 -0
- {core_framework-1.8.0/tests/integration/worker → core_framework-2.0.0/tests/integration/api/users/public}/__init__.py +0 -0
- {core_framework-1.8.0/tests/unit → core_framework-2.0.0/tests/integration/worker}/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/worker/aggregate_post_stats_test.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/worker/aggregate_user_stats_test.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/worker/avatar_upload_test.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/worker/banner_upload_test.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/worker/conftest.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/integration/worker/mute_lift_test.py +0 -0
- {core_framework-1.8.0/tests/unit/application/comments → core_framework-2.0.0/tests/unit}/__init__.py +0 -0
- {core_framework-1.8.0/tests/unit/domains → core_framework-2.0.0/tests/unit/application/comments}/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/unit/application/events/event_service_test.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/unit/core/bundled_alembic_test.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/unit/core/migrate_cli_test.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/unit/core/pagination_test.py +0 -0
- {core_framework-1.8.0/tests/unit/domains/comment → core_framework-2.0.0/tests/unit/domains}/__init__.py +0 -0
- {core_framework-1.8.0/tests/unit/infrastructure → core_framework-2.0.0/tests/unit/domains/comment}/__init__.py +0 -0
- {core_framework-1.8.0 → core_framework-2.0.0}/tests/unit/domains/user/service_test.py +0 -0
- {core_framework-1.8.0/tests/unit/infrastructure/media → core_framework-2.0.0/tests/unit/infrastructure}/__init__.py +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Alembic revision id format — short random hex, not descriptive slugs
|
|
3
|
+
globs: alembic/**/alembic/versions/*.py
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Alembic revision IDs
|
|
8
|
+
|
|
9
|
+
`alembic_version.version_num` is a **32-character** string. Revision ids must stay well under that limit.
|
|
10
|
+
|
|
11
|
+
## New revisions (required)
|
|
12
|
+
|
|
13
|
+
- Set `revision` to a **12-character lowercase hex** string (Alembic default), e.g. `a56bc6b7f799`.
|
|
14
|
+
- Generate with `python -c "import secrets; print(secrets.token_hex(6))"` or `alembic revision`.
|
|
15
|
+
- Put the human-readable slug in the **filename** and the module docstring `Revision ID:` line only if you mirror the hex id there — **do not** use descriptive slugs as `revision`.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
# ✅ Preferred
|
|
19
|
+
"""replace deleted post status with tombstone statuses
|
|
20
|
+
|
|
21
|
+
Revision ID: 120bf6be81eb
|
|
22
|
+
Revises: a56bc6b7f799
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
revision: str = "120bf6be81eb"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
# ❌ Avoid — long, wastes version_num budget, inconsistent with recent migrations
|
|
30
|
+
revision: str = "post_tombstone_status_model"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Exceptions
|
|
34
|
+
|
|
35
|
+
- Collapsed **`v1_*_init`** baseline revisions keep their stable names (`v1_post_init`, etc.) — do not rename shipped baselines.
|
|
36
|
+
|
|
37
|
+
## Do not rename shipped revision ids
|
|
38
|
+
|
|
39
|
+
If a revision may already be applied in any environment, **do not** change its `revision` string. Add a forward migration instead.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Anti-enumeration HTTP errors; uniform detail for indistinguishable outcomes
|
|
3
|
+
globs:
|
|
4
|
+
- core_framework/api/**/router.py
|
|
5
|
+
- core_framework/application/**/*_service.py
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# API Security: Missing resources and enumeration
|
|
9
|
+
|
|
10
|
+
**Policy**: Limit what clients can infer about ids, resource types, and internal state from HTTP **`detail`** text and from distinguishable error bodies. Use **catalog generic `detail`** strings per **`client-error-visibility.mdc`**. See **`docs/platform/client-error-visibility.md`**.
|
|
11
|
+
|
|
12
|
+
## Read-by-id and sensitive lookups
|
|
13
|
+
|
|
14
|
+
When a client references a resource by id and the row is **missing, not visible, or withheld** from that viewer, prefer **one client-visible outcome** per endpoint class:
|
|
15
|
+
|
|
16
|
+
- Same **HTTP status** where flow docs align cases (often **404**).
|
|
17
|
+
- Same **`detail`** — **`GENERIC_NOT_FOUND_DETAIL`**, not resource-specific copy.
|
|
18
|
+
|
|
19
|
+
This applies to public post/comment fetches, hidden/deleted/blocked cases documented as indistinguishable from unknown ids, and similar flows.
|
|
20
|
+
|
|
21
|
+
**Admin** and **internal** routes may use clearer outcomes when the actor is trusted; document exceptions in flow docs.
|
|
22
|
+
|
|
23
|
+
## Mutations and idempotency
|
|
24
|
+
|
|
25
|
+
- **Idempotent** relationship writes (block, follow, report on missing target): **204** without leaking existence — already used for block/follow on unknown ids where documented.
|
|
26
|
+
- **Idempotent DELETE** may return **204** when already gone.
|
|
27
|
+
- Do not add resource-specific error text that helps bots map id validity.
|
|
28
|
+
|
|
29
|
+
## List endpoints
|
|
30
|
+
|
|
31
|
+
Returning an empty page (`items: []`) when nothing matches remains acceptable.
|
|
32
|
+
|
|
33
|
+
## Explicit existence checks
|
|
34
|
+
|
|
35
|
+
Endpoints designed to answer “does this username exist?” (and similar) are **intentional** product surface. Treat them as such: document, rate-limit, monitor — do not rely on generic errors on other routes to replace them.
|
|
36
|
+
|
|
37
|
+
## Application vs domain signals
|
|
38
|
+
|
|
39
|
+
- **Application** exceptions → handlers in **`setup.py`**; map to HTTP status; client **`detail`** may be useful fixed copy or catalog where approved — **per-type exposure TBD**.
|
|
40
|
+
- **Typed domain** exceptions → **internal**; application translates to **`None`**, empty results, or application raises — never imported in API/application.
|
|
41
|
+
- **`BaseXxxException`** → **500** catch-all only when a typed domain exception leaks past application (missing translation).
|
|
42
|
+
- Do not raise or import domain **`PostNotFoundException`** / **`CommentNotFoundException`** from application; use application types when a detail route must error.
|
|
43
|
+
|
|
44
|
+
## Self routes
|
|
45
|
+
|
|
46
|
+
`GET /users/me/*` may use defaults or self-heal; document exceptions. Registration/login flows may create rows — not an enumeration vector for arbitrary ids.
|
|
@@ -99,9 +99,14 @@ class LoginRequest(BaseModel):
|
|
|
99
99
|
4. **Add descriptive error messages** to help API consumers understand requirements
|
|
100
100
|
5. **Document validation rules** in OpenAPI schema (automatically done by FastAPI/Pydantic)
|
|
101
101
|
|
|
102
|
+
## Domain backstops
|
|
103
|
+
|
|
104
|
+
Domain services may re-check the same rules for workers and defense-in-depth. Those failures must **not** become a second client messaging channel: **`DomainValidationError`** at HTTP returns **`GENERIC_BAD_REQUEST_DETAIL`** only. See **`client-error-visibility.mdc`** and **`docs/platform/client-error-visibility.md`**.
|
|
105
|
+
|
|
102
106
|
## Related Files
|
|
103
107
|
|
|
104
108
|
- Database migrations: `alembic/*/alembic/versions/*.py`
|
|
105
109
|
- API schemas: `core_framework/api/*/schemas.py`
|
|
106
110
|
- User shared schemas: `core_framework/api/users/shared/schemas.py`
|
|
107
111
|
- Auth schemas: `core_framework/api/auth/schemas.py`
|
|
112
|
+
- Client error policy: `docs/platform/client-error-visibility.md`
|
|
@@ -15,14 +15,18 @@ Services must NOT handle HTTP concerns. They are shared by both API and worker c
|
|
|
15
15
|
- **Do NOT** raise `HTTPException` or any FastAPI/Starlette HTTP types
|
|
16
16
|
- **Do NOT** import from `fastapi` for HTTP-specific logic (status codes, responses)
|
|
17
17
|
- **Do NOT** assume the caller is an HTTP request
|
|
18
|
+
- **Do NOT** import or raise **domain exceptions** (typed or base) — see **`domain-imports.mdc`**
|
|
18
19
|
|
|
19
|
-
**Signal
|
|
20
|
-
|
|
21
|
-
- **
|
|
20
|
+
**Signal outcomes** by returning `None`/`Optional`/empty collections, or by raising **application** exceptions from **`application.shared.exceptions`**. **Which application types expose client `detail` is TBD** per type and surface.
|
|
21
|
+
|
|
22
|
+
- **Admin routes that load a user row for a detail payload**: missing user → **`UserNotFoundException`** (application) when the service resolves that user and signals not found (handler exposure **TBD**).
|
|
23
|
+
- **Public detail routes for posts/comments by id**: missing/unavailable → **`ContentNotFoundException`** (application), or return **`None`** when the router maps not-found — **not** domain **`PostNotFoundException`** / **`CommentNotFoundException`**.
|
|
22
24
|
- **Admin list routes**: returning an empty list when a filter matches nothing is acceptable.
|
|
23
25
|
- **Admin DELETE routes**: idempotent 204 for already-missing resources is acceptable when the contract is delete-by-id.
|
|
24
26
|
- **Public/authenticated list routes**: returning an empty list is acceptable.
|
|
25
|
-
- **Workers**: handle `None` or
|
|
27
|
+
- **Workers**: handle `None` or swallow expected domain-side failures according to job policy; do not let typed domain exceptions escape to HTTP.
|
|
28
|
+
|
|
29
|
+
Translate domain failures inside application orchestration — domain services may raise typed exceptions internally, but application must not import those types. Prefer domain methods that return plain outcomes where that avoids exception coupling.
|
|
26
30
|
|
|
27
31
|
```python
|
|
28
32
|
# ✅ Raise UserNotFoundException when a referenced user id has no row (after strong read when required)
|
|
@@ -41,8 +45,17 @@ async def get_some_resource(*, resource_id: str) -> dict | None:
|
|
|
41
45
|
async def retrieve_user_detail(*, user_id: UserID) -> dict[str, Any]:
|
|
42
46
|
if not found:
|
|
43
47
|
raise HTTPException(status_code=404, detail="User not found") # ❌
|
|
48
|
+
|
|
49
|
+
# ❌ WRONG: domain exception in application
|
|
50
|
+
from core_framework.domains.post import PostNotFoundException # ❌
|
|
44
51
|
```
|
|
45
52
|
|
|
53
|
+
## Application exceptions
|
|
54
|
+
|
|
55
|
+
- Raise types from **`application.shared.exceptions`** for HTTP-meaningful use-case outcomes — not **`HTTPException`**, not typed domain exceptions.
|
|
56
|
+
- Types include **`ContentNotFoundException`**, **`ConflictException`**, ingest exceptions, and other application boundary types in that module.
|
|
57
|
+
- **Client `detail` exposure** for each type (and public vs admin) is **TBD** — use fixed reviewed copy in handlers when exposing; do not rely on ad-hoc **`message=`** at raise sites long term.
|
|
58
|
+
|
|
46
59
|
## No Pydantic Models as Return Types
|
|
47
60
|
|
|
48
61
|
Services must return plain Python types—not Pydantic API/response `BaseModel` subclasses. Routers use `response_model`; FastAPI converts the return value to the Pydantic model automatically.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Application-owned HTTP errors; domain typed exceptions internal; base catch-all only
|
|
3
|
+
globs:
|
|
4
|
+
- core_framework/core/exception_handlers/**/*.py
|
|
5
|
+
- core_framework/domains/**/exceptions.py
|
|
6
|
+
- core_framework/domains/**/*.py
|
|
7
|
+
- core_framework/application/**/*.py
|
|
8
|
+
- core_framework/infrastructure/**/*.py
|
|
9
|
+
- core_framework/api/**/*.py
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Client error visibility
|
|
13
|
+
|
|
14
|
+
Platform contract: **`docs/platform/client-error-visibility.md`**. Enumeration policy: **`api-security.mdc`**. Import boundaries: **`domain-imports.mdc`**.
|
|
15
|
+
|
|
16
|
+
## Layer model
|
|
17
|
+
|
|
18
|
+
| Layer | Outward errors | HTTP |
|
|
19
|
+
|-------|----------------|------|
|
|
20
|
+
| **Application** | **`application.shared.exceptions`** | Handlers map type → status + `detail` (**TBD** per type) |
|
|
21
|
+
| **Domain typed** | Internal only — **not** imported by API/application | No typed handlers (target) |
|
|
22
|
+
| **Domain `BaseXxxException`** | Exported for handlers only | **500** catch-all if leaked |
|
|
23
|
+
| **API** | Pydantic | **422** |
|
|
24
|
+
|
|
25
|
+
## Application exceptions
|
|
26
|
+
|
|
27
|
+
- **Only** channel for HTTP-meaningful use-case outcomes from orchestration.
|
|
28
|
+
- **May** expose client-useful fixed copy where product approves — **which types/surfaces TBD**.
|
|
29
|
+
- Prefer fixed reviewed handler strings — not ad-hoc per-call-site **`exc.message`**.
|
|
30
|
+
- Application **must not** import or raise typed domain exceptions.
|
|
31
|
+
|
|
32
|
+
## Domain exceptions (internal)
|
|
33
|
+
|
|
34
|
+
- **Typed** exceptions inherit that domain's **`BaseXxxException`** (`PostNotFoundException`, etc.) — each **`BaseXxxException`** subclasses **`BaseDomainException`**.
|
|
35
|
+
- **`DomainValidationError`** inherits **`BaseDomainException`** (`domains/exceptions.py`) — domain-only; dedicated handler → **`GENERIC_BAD_REQUEST_DETAIL`** (400).
|
|
36
|
+
- **`setup.py`** registers one **`BaseDomainException`** leak handler (**500**) and **`Exception`** unmapped handler (**500**).
|
|
37
|
+
- **`__init__.py` exports only `BaseXxxException`** per domain — not typed subclasses, not **`DomainValidationError`**.
|
|
38
|
+
- Leaked typed exception → **`BaseDomainException`** handler → **`GENERIC_INTERNAL_SERVER_ERROR_DETAIL`** (500).
|
|
39
|
+
|
|
40
|
+
## Catalog constants (`exception_handlers.common`)
|
|
41
|
+
|
|
42
|
+
| Constant | Status |
|
|
43
|
+
|----------|--------|
|
|
44
|
+
| **`GENERIC_BAD_REQUEST_DETAIL`** | 400 |
|
|
45
|
+
| **`GENERIC_NOT_FOUND_DETAIL`** | 404 |
|
|
46
|
+
| **`GENERIC_FORBIDDEN_DETAIL`** | 403 |
|
|
47
|
+
| **`GENERIC_CONFLICT_DETAIL`** | 409 |
|
|
48
|
+
| **`GENERIC_INGEST_BODY_TOO_LARGE_DETAIL`** | 413 |
|
|
49
|
+
| **`GENERIC_INGEST_UNSUPPORTED_MEDIA_TYPE_DETAIL`** | 415 |
|
|
50
|
+
| **`GENERIC_INTERNAL_SERVER_ERROR_DETAIL`** | 500 |
|
|
51
|
+
|
|
52
|
+
Application handlers use catalog and/or approved fixed copy.
|
|
53
|
+
|
|
54
|
+
## Anti-enumeration
|
|
55
|
+
|
|
56
|
+
- No resource-type phrases in catalog **`detail`** on public routes unless application exposure explicitly approves.
|
|
57
|
+
- Align missing / hidden / forbidden to the same catalog **`detail`** on read-by-id routes when flow docs require it.
|
|
58
|
+
|
|
59
|
+
## HTTP validation first
|
|
60
|
+
|
|
61
|
+
Shape, limits, duplicates, self-block, self-follow → **API schemas/routers** (422 or catalog 400). Domain backstops → **`DomainValidationError`** → generic 400 at handler.
|
|
62
|
+
|
|
63
|
+
## Logging
|
|
64
|
+
|
|
65
|
+
**Do not** **`logger.error`** at every domain **`raise`**.
|
|
66
|
+
|
|
67
|
+
| Tier | Handler log |
|
|
68
|
+
|------|-------------|
|
|
69
|
+
| Expected application 4xx | omit or **debug** |
|
|
70
|
+
| **`DomainValidationError`** | **warning** or **info** |
|
|
71
|
+
| **`BaseXxxException`** / unmapped | **error** / **exception** |
|
|
72
|
+
|
|
73
|
+
## When editing
|
|
74
|
+
|
|
75
|
+
Apply to new handlers and touched raise sites. Repo-wide migration only when scoped to client error visibility or exception boundary work.
|
|
@@ -13,6 +13,7 @@ When the user asks for a **review**, **audit**, or similar (e.g. "review my code
|
|
|
13
13
|
- **Wrong** behavior vs design docs, API contracts, or project rules
|
|
14
14
|
- **Improvements** and **optimizations** worth making
|
|
15
15
|
- **Contradictions** between code and documented intent (including doc drift)
|
|
16
|
+
- **Parallel implementation drift** — sibling modules or flows with the same job but different structure (async layout, cleanup ordering, serialization, error/idempotency contracts) without a documented reason; see the **code-review** skill checklist
|
|
16
17
|
|
|
17
18
|
Prioritize by severity. Include enough context (file, behavior, suggested fix direction) to act on each item.
|
|
18
19
|
|
|
@@ -31,9 +31,8 @@ Domain layer names must describe domain concepts and data semantics, not who cal
|
|
|
31
31
|
- `select_comments_unfiltered` ✅
|
|
32
32
|
- `PostWithMetadata` ✅
|
|
33
33
|
- Use constraint-oriented naming for variants:
|
|
34
|
-
- `
|
|
35
|
-
- `
|
|
36
|
-
- `set_post_status_for_author` ✅
|
|
34
|
+
- `tombstone_post` ✅
|
|
35
|
+
- `detach_post_authors_by_author_id` ✅
|
|
37
36
|
- Prefer naming by data shape or business rule, not persona:
|
|
38
37
|
- `select_post_with_metadata_by_id` ✅
|
|
39
38
|
- `retrieve_user_for_detail` ✅
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Enforce importing from domain __init__.py in API/application layers
|
|
3
|
+
globs:
|
|
4
|
+
- core_framework/api/**/*.py
|
|
5
|
+
- core_framework/application/**/*.py
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Domain Import Rules
|
|
9
|
+
|
|
10
|
+
When importing from domain layers (`core_framework/domains/*`) into the API/application layers (`core_framework/api/*`, `core_framework/application/*`), you MUST follow these rules:
|
|
11
|
+
|
|
12
|
+
## ✅ Allowed Imports
|
|
13
|
+
|
|
14
|
+
### 1. Import from domain `__init__.py` (Public API)
|
|
15
|
+
```python
|
|
16
|
+
# CORRECT: Import enums, models, constants from __init__.py
|
|
17
|
+
from core_framework.domains.moderation import AppealDecision, RestrictionType
|
|
18
|
+
from core_framework.domains.moderation import UserModeration, UserRestriction
|
|
19
|
+
|
|
20
|
+
from core_framework.domains.user import UserRole, BlockedUser, Profile
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Do not import domain exceptions** in API or application — typed or base. Application uses **`application.shared.exceptions`**. Exception handlers import **`BaseDomainException`** and **`DomainValidationError`** from **`domains.exceptions`** only (single leak + backstop wiring).
|
|
24
|
+
|
|
25
|
+
Translate domain outcomes in application via **`None`**, empty collections, or application exception raises after domain service calls — not by catching typed domain exceptions.
|
|
26
|
+
|
|
27
|
+
### 2. Import from `dependencies.py` (Dependency Injection)
|
|
28
|
+
```python
|
|
29
|
+
# CORRECT: Import services from dependencies
|
|
30
|
+
from core_framework.domains.moderation.dependencies import moderation_service
|
|
31
|
+
from core_framework.domains.user.dependencies import user_service
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## ❌ Prohibited Imports
|
|
35
|
+
|
|
36
|
+
### DO NOT import domain exceptions in API or application
|
|
37
|
+
```python
|
|
38
|
+
# WRONG: No domain exceptions in API/application
|
|
39
|
+
from core_framework.domains.post import PostNotFoundException # ❌
|
|
40
|
+
from core_framework.domains.moderation import AppealAlreadyDecidedException # ❌
|
|
41
|
+
from core_framework.domains.user import BaseUserException # ❌
|
|
42
|
+
from core_framework.domains.post.exceptions import PostNotFoundException # ❌
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### DO NOT import directly from other internal domain modules
|
|
46
|
+
```python
|
|
47
|
+
# WRONG: Don't import from internal modules
|
|
48
|
+
from core_framework.domains.moderation.models import UserModeration # ❌
|
|
49
|
+
from core_framework.domains.moderation.enums import AppealDecision # ❌
|
|
50
|
+
from core_framework.domains.moderation.repository import ModerationRepository # ❌
|
|
51
|
+
from core_framework.domains.moderation.service import ModerationService # ❌
|
|
52
|
+
|
|
53
|
+
from core_framework.domains.user.models import BlockedUser # ❌
|
|
54
|
+
from core_framework.domains.user.enums import UserRole # ❌
|
|
55
|
+
from core_framework.domains.user.exceptions import DomainUserNotFoundException # ❌
|
|
56
|
+
from core_framework.domains.user.repository import UserRepository # ❌
|
|
57
|
+
from core_framework.domains.user.service import UserService # ❌
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Why This Rule Exists
|
|
61
|
+
|
|
62
|
+
1. **Encapsulation**: Typed domain exceptions are internal control-flow signals
|
|
63
|
+
2. **Application boundary**: Outward HTTP errors live in **`application.shared.exceptions`**
|
|
64
|
+
3. **Maintainability**: Domain exception refactors do not break application/API
|
|
65
|
+
4. **Clear boundaries**: API/application access models/enums/services — not domain error types
|
|
66
|
+
5. **Leak detection**: Only **`BaseXxxException`** handlers at HTTP; leaks → **500**
|
|
67
|
+
|
|
68
|
+
## Domain `__init__.py` export policy
|
|
69
|
+
|
|
70
|
+
Export a symbol from a domain's **`__init__.py`** only when an outer layer needs it.
|
|
71
|
+
|
|
72
|
+
- **Do export**: models, enums, constants consumed by API, application, or adapters.
|
|
73
|
+
- **Exceptions**: export **only** **`BaseXxxException`** per domain (subclasses **`BaseDomainException`**; used for typing and domain-internal inheritance — **not** for per-domain HTTP handler registration).
|
|
74
|
+
- **Do not export**: persistence/registry shapes, repository row mappings, typed exceptions, and other types used only inside **`domains/<name>/`**.
|
|
75
|
+
|
|
76
|
+
Every **typed** exception defined under a domain must **inherit** from that domain's **`BaseXxxException`**. **`DomainValidationError`** is the shared backstop in **`domains/exceptions.py`**: it inherits **`BaseDomainException`** (cross-domain root), **not** a per-domain **`BaseXxxException`**. Handler wiring imports **`DomainValidationError`** from **`domains.exceptions`** in core only — it is not exported from domain **`__init__.py`**.
|
|
77
|
+
|
|
78
|
+
When adding a new exception, default to **internal** (`exceptions.py` only). Add **`BaseXxxException`** to **`__init__.py`** when the domain needs a typed base for internal inheritance — never export the typed subclasses. HTTP leak detection uses the shared **`BaseDomainException`** handler, not per-domain handler modules.
|
|
79
|
+
|
|
80
|
+
## Available Domain Exports
|
|
81
|
+
|
|
82
|
+
### Moderation Domain (`from core_framework.domains.moderation import ...`)
|
|
83
|
+
- **Enums**: `RestrictionType`, `AppealDecision`, `ReportType`
|
|
84
|
+
- **Models**: `Report`, `UserModeration`, `UserRestriction`
|
|
85
|
+
- **Outcomes**: `AddAppealOutcome`, `AddUserReportOutcome`, `DecideAppealOutcome`
|
|
86
|
+
- **Exceptions**: `BaseModerationException` only (handler catch-all)
|
|
87
|
+
- **Service**: Use `moderation_service` from `dependencies.py`
|
|
88
|
+
- **Internal** (not in `__init__.py`): `AppealNotFoundException`, `AppealAlreadyDecidedException`, `SelfReportException`, `ExistingPendingAppealException`, `AppealRequirementException`, and other typed exceptions
|
|
89
|
+
|
|
90
|
+
### User Domain (`from core_framework.domains.user import ...`)
|
|
91
|
+
- **Enums**: `UserRole`
|
|
92
|
+
- **Models**: `CreatedUser`, `BlockedUser`, `Profile`, `UserIdentity`
|
|
93
|
+
- **Outcomes**: `AddUserOutcome`, `BlockUserOutcome`, `ChangeAccountOutcome`, `FollowUserOutcome`
|
|
94
|
+
- **Exceptions**: `BaseUserException` only (handler catch-all)
|
|
95
|
+
- **Service**: Use `user_service` from `dependencies.py`
|
|
96
|
+
- **Internal** (not in `__init__.py`): `DomainUserNotFoundException`, `SelfBlockException`, `UserIdConflictException`, `UsernameConflictException`, `UserCreationException`
|
|
97
|
+
|
|
98
|
+
### Post Domain (`from core_framework.domains.post import ...`)
|
|
99
|
+
- **Outcomes**: `PostEditOutcome`
|
|
100
|
+
- **Exceptions**: `BasePostException` only (handler catch-all)
|
|
101
|
+
- **Internal**: `PostNotFoundException`, edit-limit types, and all other typed exceptions
|
|
102
|
+
|
|
103
|
+
### Comment Domain (`from core_framework.domains.comment import ...`)
|
|
104
|
+
- **Outcomes**: `CommentEditOutcome`, `ReplyCreateOutcome`
|
|
105
|
+
- **Exceptions**: `BaseCommentException` only (handler catch-all)
|
|
106
|
+
- **Internal**: `CommentNotFoundException`, edit-limit types, and all other typed exceptions
|
|
107
|
+
|
|
108
|
+
### Media Domain (`from core_framework.domains.media import ...`)
|
|
109
|
+
- **Constants**: ingest limits, variant constants
|
|
110
|
+
- **Models**: `AvatarIngestRegistration`, `AvatarVariantBytes`, etc.
|
|
111
|
+
- **Outcomes**: `IngestContentTypeOutcome`, `IngestBodyOutcome`, `IngestLeaseOutcome`, `AttachmentStagingImageProbeOutcome`
|
|
112
|
+
- **Exceptions**: `BaseMediaException` only (handler catch-all)
|
|
113
|
+
- **Internal**: ingest, staging, and publish typed exceptions
|
|
114
|
+
- **Internal** (not in `__init__.py`): `AvatarStagingRegistryEntry`, `AttachmentStagingRegistryEntry`
|
|
115
|
+
|
|
116
|
+
## How to Fix Violations
|
|
117
|
+
|
|
118
|
+
If application needs to signal an HTTP outcome after a domain call:
|
|
119
|
+
|
|
120
|
+
1. **Return `None` or empty** when the use case allows it (lists, optional reads, idempotent deletes).
|
|
121
|
+
2. **Raise an application exception** from **`application.shared.exceptions`** (add a type if needed).
|
|
122
|
+
3. **Extend the domain service API** so typed exceptions are absorbed inside the domain and the caller sees a plain outcome — do not export the typed exception.
|
|
123
|
+
|
|
124
|
+
Repositories are only wired for domain services and tests. Application code never imports them.
|
|
@@ -11,7 +11,7 @@ Use **layered** checks: user contracts at the **API**, business preconditions at
|
|
|
11
11
|
|
|
12
12
|
## Policy: validate in the service; database is last line of defense
|
|
13
13
|
|
|
14
|
-
- **Domain services** should enforce **policy and contract** as much as practical **before** data reaches Postgres: sane limits, non-negative counts where required, empty batches with obvious safe behavior, meaningless argument combinations, etc. Goals:
|
|
14
|
+
- **Domain services** should enforce **policy and contract** as much as practical **before** data reaches Postgres: sane limits, non-negative counts where required, empty batches with obvious safe behavior, meaningless argument combinations, etc. Goals: fail fast with **`DomainValidationError`** (internal/backstop — HTTP **`GENERIC_BAD_REQUEST_DETAIL`**) or **typed domain exceptions** (internal control flow — application translates to outward outcomes), and **one** contract for workers and future callers.
|
|
15
15
|
- **Do not rely** on CHECK, NOT NULL, FK, or UNIQUE as the *main* way invalid **application** input is caught when the service can reject it cheaply. Constraints still **must** exist where they express **data integrity** (what must never be stored wrong).
|
|
16
16
|
- **Database constraints** remain **mandatory failsafes**: bugs, race conditions, code that bypasses the service, ad-hoc scripts, or missed branches. When they fire, treat that as exceptional—log/monitor and map or handle integrity errors appropriately; prefer never to depend on that path for normal validation UX.
|
|
17
17
|
|
|
@@ -27,7 +27,7 @@ Use **layered** checks: user contracts at the **API**, business preconditions at
|
|
|
27
27
|
- **Purpose:** Preconditions and policy that apply to **any** caller that correctly uses **`{Domain}Service`** as the entrypoint.
|
|
28
28
|
- **Prefer guards here** for rules like: “empty batch → default mapping / skip query,” “limit in allowed range,” “counts non-negative,” “this combination of arguments is meaningless.”
|
|
29
29
|
- Keeps **repositories thin** (data access + SQL) and gives **one** canonical place for domain rules above persistence.
|
|
30
|
-
- Prefer **early return** when the safe behavior is obvious (e.g. empty `comment_ids` → empty map). Use **`DomainValidationError`**
|
|
30
|
+
- Prefer **early return** when the safe behavior is obvious (e.g. empty `comment_ids` → empty map). Use **`DomainValidationError`** when invalid input is a **caller** mistake that must not become **500** — HTTP maps it to generic **400**. Use **typed domain exceptions** for in-domain non-500 control flow; application translates before HTTP — typed exceptions are **not** exported or registered as HTTP handlers (see **`client-error-visibility.mdc`**). User-fixable **input shape** belongs in API Pydantic validation (**422**) first.
|
|
31
31
|
|
|
32
32
|
### Shared string / pagination helpers (`core_framework.domains.utils`)
|
|
33
33
|
|
|
@@ -38,7 +38,7 @@ Use these across domains so validation stays consistent and call sites stay read
|
|
|
38
38
|
| Helper | Use when |
|
|
39
39
|
|--------|----------|
|
|
40
40
|
| **`require_non_blank(value=..., field=...)`** | A single required string must not be empty or whitespace-only. |
|
|
41
|
-
| **`require_non_blank_kwargs(a_id=..., b_id=...)`** | Several required strings at once; each **keyword name** is the field label in `DomainValidationError` (
|
|
41
|
+
| **`require_non_blank_kwargs(a_id=..., b_id=...)`** | Several required strings at once; each **keyword name** is the field label in internal `DomainValidationError` messages (for logs — not forwarded to HTTP **`detail`**). |
|
|
42
42
|
| **`require_non_blank_if_not_none(viewer_id=..., ...)`** | Optional `str \| None` arguments: validate only when not `None` (still rejects `""`). |
|
|
43
43
|
| **`validate_pagination_limit(limit=...)`** | Paginated reads: `limit` in `[1, MAX_PAGE_FETCH_SIZE]` (aligned with `core_framework.core.pagination`). |
|
|
44
44
|
| **`validate_pagination_offset(offset=...)`** | Paginated reads: `offset` must be **≥ 0** (aligned with `OffsetQueryParams`). |
|
|
@@ -65,7 +65,7 @@ Prefer **`require_non_blank_kwargs`** / **`require_non_blank_if_not_none`** over
|
|
|
65
65
|
| Layer | Role |
|
|
66
66
|
|--------------------|------|
|
|
67
67
|
| API (Pydantic) | User input, **422**, documented request contract |
|
|
68
|
-
| **Domain service** | **Primary** business preconditions for all service callers; validate before persistence where practical |
|
|
68
|
+
| **Domain service** | **Primary** business preconditions for all service callers; validate before persistence where practical; HTTP sees generic **400** for **`DomainValidationError`** unless API already validated |
|
|
69
69
|
| Repository | **Optional** narrow checks before catastrophic / meaningless SQL |
|
|
70
70
|
| Database | **Ultimate** integrity; last line of defense—not relied on for normal invalid-input UX |
|
|
71
71
|
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
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 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 channels
|
|
18
|
+
|
|
19
|
+
### Application exceptions (`setup.py`)
|
|
20
|
+
|
|
21
|
+
- Register types from **`application.shared.exceptions`**.
|
|
22
|
+
- Map **type → HTTP status**; **`detail`** per application exposure policy (**TBD**; catalog generics and/or fixed reviewed strings).
|
|
23
|
+
- Register **specific** application types before any application base catch-all if one is added later.
|
|
24
|
+
|
|
25
|
+
### Domain exceptions (catch-all only)
|
|
26
|
+
|
|
27
|
+
- Every **`BaseXxxException`** subclasses **`BaseDomainException`** (`domains/exceptions.py`). Typed domain exceptions inherit **`BaseXxxException`**.
|
|
28
|
+
- Register **one** **`base_domain_leak_exception_handler`** on **`BaseDomainException`** in **`setup.py`** → **500** + **`GENERIC_INTERNAL_SERVER_ERROR_DETAIL`** + **error**/**exception** log.
|
|
29
|
+
- **`DomainValidationError`** also subclasses **`BaseDomainException`** but has a dedicated **400** handler registered on the subclass (MRO picks it before the leak handler).
|
|
30
|
+
- Typed domain exceptions must **not** have dedicated HTTP handlers; if one leaks past application, the **`BaseDomainException`** handler fires.
|
|
31
|
+
|
|
32
|
+
### Unmapped exceptions
|
|
33
|
+
|
|
34
|
+
- Register **`unmapped_exception_handler`** on **`Exception`** in **`setup.py`** after application and domain handlers — **500** catalog for bugs outside mapped types. Framework types (**`HTTPException`**, **`RequestValidationError`**) keep their own handlers via exact-type lookup.
|
|
35
|
+
|
|
36
|
+
### `DomainValidationError`
|
|
37
|
+
|
|
38
|
+
- Wired from **`core_framework.domains.exceptions`** (internal import — not domain **`__init__.py`** export).
|
|
39
|
+
- Subclasses **`BaseDomainException`** (shared root) — **not** **`BasePostException`** / per-domain leak bases.
|
|
40
|
+
- **400** + **`GENERIC_BAD_REQUEST_DETAIL`**; log at **warning**/**info** — not **`exc.message`** in **`detail`**.
|
|
41
|
+
- Separate from the **`BaseDomainException`** leak handler (**500**); a **`DomainValidationError`** does not trigger leak detection.
|
|
42
|
+
|
|
43
|
+
## Handler functions
|
|
44
|
+
|
|
45
|
+
- One async handler per mapped exception type: **`(Request, ExcT) -> JSONResponse`**.
|
|
46
|
+
- Type **`exc`** as the **specific** registered exception (not bare **`Exception`**).
|
|
47
|
+
- Handlers translate exception → HTTP response only — no business logic or repository calls.
|
|
48
|
+
- **Do not** log at **error** for expected application 4xx unless the task explicitly requires it.
|
|
49
|
+
|
|
50
|
+
## Module layout
|
|
51
|
+
|
|
52
|
+
- **`setup.py`** registers application handlers, **`DomainValidationError`**, **`BaseDomainException`** leak handler, and **`Exception`** unmapped handler.
|
|
53
|
+
- Do **not** add per-domain handler modules for **`BasePostException`** / **`BaseUserException`** etc. — one **`BaseDomainException`** registration covers all domain leaks.
|
|
54
|
+
|
|
55
|
+
## Avoid
|
|
56
|
+
|
|
57
|
+
- Registering typed domain exceptions as HTTP handlers in new code.
|
|
58
|
+
- Importing typed domain exceptions into handler modules.
|
|
59
|
+
- Per-call-site **`ty: ignore[invalid-argument-type]`** or **`cast(ExceptionHandler, ...)`** on registrations.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Raise exceptions with inline message strings; do not assign msg solely for the raise on the next line.
|
|
3
|
+
globs: ["core_framework/**/*.py", "tests/**/*.py"]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Inline raise messages
|
|
7
|
+
|
|
8
|
+
Do **not** assign an exception message to a variable on the line immediately before `raise` when that variable exists only to pass the string into the exception.
|
|
9
|
+
|
|
10
|
+
## Preferred
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
# ✅ Single-line message
|
|
14
|
+
raise ValueError("title must not be empty or whitespace-only")
|
|
15
|
+
|
|
16
|
+
# ✅ f-string
|
|
17
|
+
raise ValueError(f"resource must be one of: {allowed}")
|
|
18
|
+
|
|
19
|
+
# ✅ Long message — parenthesized string on raise (still no msg variable)
|
|
20
|
+
raise RuntimeError(
|
|
21
|
+
"Core runtime is not configured on app.state. "
|
|
22
|
+
"Build the app via init_app() or assign app.state.core_runtime during bootstrap."
|
|
23
|
+
)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Avoid
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
# ❌ Two-line raise solely to hold the message
|
|
30
|
+
msg = "title must not be empty or whitespace-only"
|
|
31
|
+
raise ValueError(msg)
|
|
32
|
+
|
|
33
|
+
# ❌ Same for other exception types
|
|
34
|
+
msg = f"no alembic.ini for domain {domain!r}"
|
|
35
|
+
raise FileNotFoundError(msg)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## When a local message variable is OK
|
|
39
|
+
|
|
40
|
+
- The **same** message string is reused more than once before raising.
|
|
41
|
+
- The message is **built incrementally** across multiple statements (not a single literal/f-string assignment).
|
|
42
|
+
|
|
43
|
+
## When editing
|
|
44
|
+
|
|
45
|
+
- Apply this style to **new** raises and to any `raise` you touch in the same function or validator.
|
|
46
|
+
- Do not repo-wide rewrite unrelated legacy raises unless the task asks for it.
|
|
@@ -25,6 +25,7 @@ Use this architecture split consistently:
|
|
|
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
27
|
- Repositories are Postgres-only; filesystem, HTTP, SSH, and similar I/O use domain ports/adapters (separate collaborators), not repository methods.
|
|
28
|
+
- Repositories must not query other domains' Postgres schemas — see **`repository-schema-isolation`**.
|
|
28
29
|
- Application orchestrates multi-step flows; do not fold shared API+worker orchestration into domain services to avoid “application” existing.
|
|
29
30
|
|
|
30
31
|
## Dependency Direction
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Domain repositories must not query other domains' Postgres schemas
|
|
3
|
+
globs:
|
|
4
|
+
- core_framework/domains/**/repository.py
|
|
5
|
+
alwaysApply: false
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Repository schema isolation
|
|
9
|
+
|
|
10
|
+
Each domain repository may only read/write tables in **its own** Postgres schema.
|
|
11
|
+
|
|
12
|
+
Canonical rule: `docs/platform/layers-and-boundaries.md` — *no cross-domain schema reads, writes, joins, or foreign keys*.
|
|
13
|
+
|
|
14
|
+
## Allowed in a repository
|
|
15
|
+
|
|
16
|
+
- SQL qualified with that domain's schema only (e.g. `"post".posts` in `PostRepository`).
|
|
17
|
+
- **Within-schema** policy gates (e.g. `insert_post_like` checks `"post".posts.status = active`).
|
|
18
|
+
- **Lookup mirrors** in the same schema (`user_blocks_lookup`, `user_restrictions_lookup`, `user_followers_lookup`) — synced by application flows; mirrors are not authoritative.
|
|
19
|
+
|
|
20
|
+
## Forbidden in a repository
|
|
21
|
+
|
|
22
|
+
- `FROM` / `JOIN` / `EXISTS` / `INSERT … SELECT` against another domain's schema.
|
|
23
|
+
- Importing another domain's `service`, `repository`, or models to enforce policy.
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
# ❌ moderation/repository.py — cross-domain EXISTS
|
|
27
|
+
insert into "moderation".post_reports (...)
|
|
28
|
+
select $1, $2, $3, $4
|
|
29
|
+
where exists (
|
|
30
|
+
select 1 from "post".posts
|
|
31
|
+
where id = $2 and status = 'active'
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# ❌ notification/repository.py — cross-domain mute gate
|
|
35
|
+
insert into "notification".notification_mutes (...)
|
|
36
|
+
select ...
|
|
37
|
+
where exists (select 1 from "comment".comments where ...)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Alembic migrations (different convention)
|
|
41
|
+
|
|
42
|
+
Do **not** apply repository schema-qualification rules to `alembic/**/versions/*.py`.
|
|
43
|
+
|
|
44
|
+
Each domain Alembic tree sets `search_path` to that domain's schema in `env.py`. Migration SQL uses **unqualified** table and type names (`posts`, `post_reports`, `create type post_status …`) — not `"post".posts`.
|
|
45
|
+
|
|
46
|
+
Cross-domain rules still apply to migrations:
|
|
47
|
+
|
|
48
|
+
- No `REFERENCES` / FKs to tables in another domain's schema.
|
|
49
|
+
- No DDL that reads or writes another domain's objects.
|
|
50
|
+
|
|
51
|
+
Stay within the migration tree's domain; one schema per Alembic env.
|
|
52
|
+
|
|
53
|
+
## Where cross-domain policy belongs
|
|
54
|
+
|
|
55
|
+
**Application layer** (or domain service orchestration via dependencies, never cross-schema SQL):
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
# ✅ application/posts/authenticated_service.py
|
|
59
|
+
post = await post_deps.post_service.retrieve_post_with_metadata_by_id_if_exists(post_id=post_id)
|
|
60
|
+
if post is None or post.status != PostStatus.ACTIVE:
|
|
61
|
+
return False
|
|
62
|
+
return await moderation_deps.moderation_service.add_post_report(...)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Before adding a repository write that depends on another domain's row state:
|
|
66
|
+
|
|
67
|
+
1. Can the owning domain gate it within its schema? → do that (likes, views, stats dirty).
|
|
68
|
+
2. Otherwise → gate in `application/**/*_service.py` via the owning domain's service API.
|
|
69
|
+
|
|
70
|
+
## Self-check (repositories only)
|
|
71
|
+
|
|
72
|
+
In `domains/<name>/repository.py`, every `"<schema>".` qualifier in SQL must be `<name>` (or that domain's documented mirror tables in the same schema).
|
|
@@ -41,17 +41,21 @@ Use quotes for strings, no quotes for numbers/booleans/arrays.
|
|
|
41
41
|
|
|
42
42
|
## 4. Deploy Workflow (`.github/workflows/_deploy.yml`)
|
|
43
43
|
|
|
44
|
-
Add to the `env` block in "Render config.toml from template" step:
|
|
44
|
+
Add to the `env` block in "Render config.toml from template" step. Use **secrets** for credentials/tokens/keys; use **variables** for non-sensitive config (hosts, ports, public hostnames, paths):
|
|
45
45
|
|
|
46
46
|
```yaml
|
|
47
47
|
env:
|
|
48
48
|
# existing vars...
|
|
49
|
-
NEW_SETTING: ${{
|
|
49
|
+
NEW_SETTING: ${{ vars.NEW_SETTING }} # non-sensitive
|
|
50
|
+
NEW_SECRET: ${{ secrets.NEW_SECRET }} # password, token, or private key
|
|
50
51
|
```
|
|
51
52
|
|
|
53
|
+
See **`docs/deployments/deploy-playbook.md`** for the current secrets vs variables split.
|
|
54
|
+
|
|
52
55
|
## Reminder
|
|
53
56
|
|
|
54
|
-
After completing all steps, remind the user to add the
|
|
57
|
+
After completing all steps, remind the user to add the value to GitHub:
|
|
55
58
|
|
|
56
|
-
- Settings → Secrets and variables → Actions →
|
|
57
|
-
-
|
|
59
|
+
- **Variables:** Settings → Secrets and variables → Actions → Environments → **`development`** / **`production`** → Variables
|
|
60
|
+
- **Secrets:** same path → Environment secrets
|
|
61
|
+
- Add to **both** environments when the value differs per deploy target
|
|
@@ -181,7 +181,7 @@ Worker `startup` and API `init_app` already call `configure_application_dependen
|
|
|
181
181
|
|
|
182
182
|
Do this in a **separate step/PR** when you have DDL to apply.
|
|
183
183
|
|
|
184
|
-
1. Add **`alembic/<domain>/alembic/versions/*.py`** (at least one revision); `upgrade()` may create schema/tables/enums as designed.
|
|
184
|
+
1. Add **`alembic/<domain>/alembic/versions/*.py`** (at least one revision); `upgrade()` may create schema/tables/enums as designed. Use a **12-character lowercase hex** `revision` id (see **`alembic-revision-ids`**); put the descriptive slug in the filename only.
|
|
185
185
|
1. **Append** `<domain>` to `ALEMBIC_DOMAINS` in `core_framework/bundled_alembic.py` **in the same change** as the first revision (so `cf-alembic` / CI stay green).
|
|
186
186
|
1. `make alembic` and deploy’s `cf-alembic` step pick up the new domain automatically — no makefile or `_deploy.yml` edits needed.
|
|
187
187
|
|