core-framework 2.1.0__tar.gz → 2.3.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.1.0/.cursor/rules/alembic-revision-ids.mdc → core_framework-2.3.0/.cursor/rules/alembic-migrations.mdc +26 -5
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/api-layer.mdc +1 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/application-layer.mdc +1 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/constitution.mdc +7 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/domain-caller-context.mdc +1 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/domain-imports.mdc +2 -2
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/domain-repository-exceptions.mdc +5 -2
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/error-boundary.mdc +4 -3
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/layer-boundaries.mdc +1 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/strong-reads.mdc +4 -4
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/skills/add-domain/SKILL.md +1 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/skills/code-review/SKILL.md +1 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/skills/implement-feature/SKILL.md +1 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/.github/workflows/_deploy.yml +3 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.github/workflows/test.yaml +5 -10
- {core_framework-2.1.0 → core_framework-2.3.0}/CHANGELOG.md +52 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/PKG-INFO +1 -1
- core_framework-2.3.0/alembic/analytics/alembic/env.py +74 -0
- core_framework-2.3.0/alembic/analytics/alembic/versions/5b9ac98bb150_analytics_daily_fact.py +33 -0
- core_framework-2.3.0/alembic/analytics/alembic/versions/7f2a9c1d4e8b_analytics_daily_fact_day_index.py +27 -0
- core_framework-2.3.0/alembic/moderation/alembic/versions/moderation_add_event_outbox.py +52 -0
- core_framework-2.3.0/alembic/moderation/alembic/versions/moderation_add_event_outbox_dead_indexes.py +34 -0
- core_framework-2.3.0/alembic/moderation/alembic/versions/moderation_add_event_outbox_pending_created_at_index.py +28 -0
- core_framework-2.3.0/alembic/notification/alembic.ini +52 -0
- core_framework-2.3.0/alembic/post/alembic/versions/post_post_views_retention_high_water_mark.py +64 -0
- core_framework-2.3.0/alembic/user/alembic/README +1 -0
- core_framework-2.3.0/alembic/user/alembic/script.py.mako +28 -0
- core_framework-2.3.0/alembic/user/alembic/versions/user_add_event_outbox.py +52 -0
- core_framework-2.3.0/alembic/user/alembic/versions/user_add_event_outbox_dead_indexes.py +34 -0
- core_framework-2.3.0/alembic/user/alembic/versions/user_add_event_outbox_pending_created_at_index.py +28 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/config.toml +19 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/config.toml.template +20 -2
- core_framework-2.3.0/core_framework/api/admin/analytics/router.py +44 -0
- core_framework-2.3.0/core_framework/api/admin/analytics/schemas.py +59 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/admin/cache/router.py +2 -8
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/admin/comments/router.py +3 -13
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/admin/moderation/router.py +2 -10
- core_framework-2.3.0/core_framework/api/admin/outbox/router.py +71 -0
- core_framework-2.3.0/core_framework/api/admin/outbox/schemas.py +56 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/admin/posts/router.py +3 -12
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/admin/router.py +4 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/admin/users/router.py +2 -10
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/admin/users/schemas.py +1 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/comments/authenticated/router.py +5 -25
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/comments/authenticated/schemas.py +1 -4
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/events/router.py +12 -11
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/events/schemas.py +4 -3
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/notifications/authenticated/router.py +2 -8
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/posts/authenticated/router.py +1 -5
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/posts/authenticated/schemas.py +1 -4
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/users/authenticated/router.py +10 -41
- core_framework-2.3.0/core_framework/application/analytics/bucket_service.py +43 -0
- core_framework-2.3.0/core_framework/application/analytics/prune_service.py +6 -0
- core_framework-2.3.0/core_framework/application/analytics/read_service.py +49 -0
- core_framework-2.3.0/core_framework/application/analytics/refresh_service.py +52 -0
- core_framework-2.3.0/core_framework/application/analytics/registry.py +36 -0
- core_framework-2.3.0/core_framework/application/analytics/sources/comment_created.py +26 -0
- core_framework-2.3.0/core_framework/application/analytics/sources/post_created.py +26 -0
- core_framework-2.3.0/core_framework/application/analytics/sources/post_viewed.py +26 -0
- core_framework-2.3.0/core_framework/application/analytics/sources/user_registered.py +26 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/bootstrap.py +18 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/comments/admin_service.py +2 -10
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/comments/authenticated_service.py +5 -15
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/comments/public_service.py +2 -11
- core_framework-2.3.0/core_framework/application/events/README.md +35 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/events/event_service.py +29 -21
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/events/event_token.py +7 -12
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/events/models.py +1 -3
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/media/README.md +3 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/media/attachment_service.py +12 -23
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/media/avatar_service.py +8 -13
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/media/banner_service.py +8 -13
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/media/ingest_guards.py +1 -5
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/moderation/appeal_service.py +43 -39
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/moderation/moderator_service.py +1 -6
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/moderation/user_service.py +42 -64
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/notifications/inbox_service.py +2 -11
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/notifications/mute_service.py +1 -5
- core_framework-2.3.0/core_framework/application/outbox/block_mirrors.py +54 -0
- core_framework-2.3.0/core_framework/application/outbox/events.py +109 -0
- core_framework-2.3.0/core_framework/application/outbox/exceptions.py +2 -0
- core_framework-2.3.0/core_framework/application/outbox/follow_mirrors.py +20 -0
- core_framework-2.3.0/core_framework/application/outbox/handlers.py +115 -0
- core_framework-2.3.0/core_framework/application/outbox/ops_service.py +77 -0
- core_framework-2.3.0/core_framework/application/outbox/polling_service.py +64 -0
- core_framework-2.3.0/core_framework/application/outbox/processing_service.py +69 -0
- core_framework-2.3.0/core_framework/application/outbox/restriction_mirrors.py +46 -0
- core_framework-2.3.0/core_framework/application/posts/aggregation_service.py +54 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/posts/authenticated_service.py +2 -13
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/posts/public_service.py +7 -6
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/shared/enums.py +2 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/shared/exceptions.py +6 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/users/admin_service.py +7 -32
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/users/authenticated_service.py +53 -52
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/users/public_service.py +2 -2
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/users/user_deletion_service.py +6 -2
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/bundled_alembic.py +1 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/core/database.py +1 -6
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/core/exception_handlers/application.py +17 -28
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/core/exception_handlers/common.py +2 -8
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/core/exception_handlers/setup.py +8 -8
- core_framework-2.3.0/core_framework/core/exceptions.py +2 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/core/logging.py +1 -5
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/core/middleware.py +1 -6
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/core/pagination.py +4 -2
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/core/runtime.py +6 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/core/settings.py +81 -9
- core_framework-2.3.0/core_framework/domains/analytics/README.md +26 -0
- core_framework-2.3.0/core_framework/domains/analytics/__init__.py +13 -0
- core_framework-2.3.0/core_framework/domains/analytics/constants.py +3 -0
- core_framework-2.3.0/core_framework/domains/analytics/dependencies.py +59 -0
- core_framework-2.3.0/core_framework/domains/analytics/exceptions.py +5 -0
- core_framework-2.3.0/core_framework/domains/analytics/models.py +17 -0
- core_framework-2.3.0/core_framework/domains/analytics/ports/activity_source.py +37 -0
- core_framework-2.3.0/core_framework/domains/analytics/repository.py +176 -0
- core_framework-2.3.0/core_framework/domains/analytics/service.py +204 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/auth/service.py +4 -4
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/comment/README.md +3 -2
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/comment/repository.py +61 -83
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/comment/service.py +41 -73
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/attachment/components/finals_publisher.py +1 -7
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/attachment/ports/finals_publisher.py +1 -7
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/attachment/service.py +3 -13
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/avatar/service.py +4 -20
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/banner/service.py +3 -13
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/shared/repository.py +4 -24
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/moderation/dependencies.py +28 -2
- core_framework-2.3.0/core_framework/domains/moderation/outbox_repository.py +179 -0
- core_framework-2.3.0/core_framework/domains/moderation/outbox_service.py +52 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/moderation/repository.py +93 -26
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/moderation/service.py +86 -24
- core_framework-2.3.0/core_framework/domains/outbox/__init__.py +17 -0
- core_framework-2.3.0/core_framework/domains/outbox/dependencies.py +61 -0
- core_framework-2.3.0/core_framework/domains/outbox/models.py +79 -0
- core_framework-2.3.0/core_framework/domains/outbox/ports/admin.py +35 -0
- core_framework-2.3.0/core_framework/domains/outbox/ports/consumer.py +78 -0
- core_framework-2.3.0/core_framework/domains/outbox/ports/producer.py +42 -0
- core_framework-2.3.0/core_framework/domains/outbox/service.py +66 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/post/README.md +6 -5
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/post/models.py +12 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/post/repository.py +216 -124
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/post/service.py +122 -48
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/user/dependencies.py +28 -2
- core_framework-2.3.0/core_framework/domains/user/outbox_repository.py +178 -0
- core_framework-2.3.0/core_framework/domains/user/outbox_service.py +52 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/user/outcomes.py +1 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/user/repository.py +216 -59
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/user/service.py +127 -53
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/utils.py +15 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/infrastructure/media/ssh_attachment_finals_publisher_adapter.py +3 -21
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/infrastructure/media/ssh_banner_finals_publisher_adapter.py +2 -14
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/infrastructure/media/ssh_finals_publisher_adapter.py +2 -14
- core_framework-2.3.0/core_framework/infrastructure/outbox/outbox_consumer_adapter.py +150 -0
- core_framework-2.3.0/core_framework/infrastructure/outbox/outbox_producer_adapter.py +71 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/testing/arq.py +22 -20
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/testing/auth.py +62 -48
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/testing/containers.py +1 -5
- core_framework-2.3.0/core_framework/testing/firebase_identity_toolkit.py +20 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/testing/media.py +2 -14
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/main.py +19 -4
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/schedules/__init__.py +37 -0
- core_framework-2.3.0/core_framework/worker/schedules/analytics_refresh_cron.py +18 -0
- core_framework-2.3.0/core_framework/worker/schedules/schedule_poll_outbox.py +42 -0
- core_framework-2.3.0/core_framework/worker/schedules/schedule_prune_analytics_daily_facts.py +16 -0
- core_framework-2.3.0/core_framework/worker/schedules/schedule_prune_post_views.py +17 -0
- core_framework-2.3.0/core_framework/worker/schedules/schedule_refresh_analytics.py +7 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/schedules/schedule_sweep_stale_avatar_staging.py +2 -2
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/schedules/schedule_sweep_stale_banner_staging.py +2 -2
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/tasks/__init__.py +22 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/tasks/process_attachment_asset_variants.py +1 -6
- core_framework-2.3.0/core_framework/worker/tasks/process_outbox_row.py +7 -0
- core_framework-2.3.0/core_framework/worker/tasks/process_refresh_analytics_bucket.py +19 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/deployments/deploy-playbook.md +6 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/deployments/e2e-smoke-tests-design.md +1 -1
- core_framework-2.3.0/docs/domains/README.md +18 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/auth/access_control.md +1 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/cache/admin_cache_invalidation.md +9 -9
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/comments/admin_comments.md +1 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/deletion/comments.md +2 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/deletion/overview.md +12 -3
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/deletion/posts.md +36 -23
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/deletion/users.md +20 -19
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/events/events.md +21 -7
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/media/avatar-upload-design.md +1 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/media/banner-upload-design.md +1 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/media/post-comment-attachments-design.md +26 -27
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/media/upload-pipeline-design.md +28 -30
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/moderation/appeals.md +35 -17
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/moderation/restrictions.md +28 -22
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/notifications/notification_inbox.md +1 -1
- core_framework-2.3.0/docs/domains/outbox/admin_outbox_ops.md +182 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/posts/admin_posts.md +2 -2
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/posts/author_context.md +1 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/posts/post_like.md +2 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/posts/post_stats_aggregation.md +8 -7
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/users/account_deletion.md +2 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/users/blocks.md +11 -2
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/users/check_username_exists.md +1 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/users/follow-system-design.md +69 -61
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/users/follow.md +8 -4
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/users/user_removal.md +6 -6
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/library/api.md +8 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/library/overview.md +5 -3
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/library/package-api.md +27 -4
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/platform/README.md +15 -12
- core_framework-2.3.0/docs/platform/analytics-rollup-design.md +990 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/platform/architecture-decisions.md +75 -12
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/platform/client-error-visibility.md +1 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/platform/conventions.md +6 -8
- core_framework-2.3.0/docs/platform/event-outbox-design.md +722 -0
- core_framework-2.3.0/docs/platform/events-ingest-design.md +109 -0
- core_framework-2.3.0/docs/platform/outbox-admin-ops-design.md +334 -0
- core_framework-2.3.0/docs/todo.md +11 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/makefile +2 -2
- {core_framework-2.1.0 → core_framework-2.3.0}/pyproject.toml +1 -1
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/e2e/_auth.py +8 -5
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/e2e/conftest.py +1 -5
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/e2e/smoke/tls_host_test.py +24 -6
- core_framework-2.3.0/tests/integration/analytics/_helpers.py +135 -0
- core_framework-2.3.0/tests/integration/api/_auth_helpers.py +6 -0
- core_framework-2.3.0/tests/integration/api/_outbox_helpers.py +138 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/admin/_auth_cases.py +11 -0
- core_framework-2.3.0/tests/integration/api/admin/analytics/router_test.py +265 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/admin/cache/router_test.py +5 -25
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/admin/moderation/router_test.py +96 -10
- core_framework-2.3.0/tests/integration/api/admin/outbox/router_test.py +333 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/admin/users/router_test.py +5 -25
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/attachments/_helpers.py +3 -17
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/attachments/attachment_upload_test.py +1 -5
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/comments/authenticated/comment_writes_integration_test.py +3 -16
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/comments/public/router_test.py +19 -11
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/create_idempotency_integration_test.py +1 -6
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/events/router_test.py +4 -16
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/notifications/router_test.py +5 -13
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/posts/authenticated/post_writes_integration_test.py +1 -6
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/posts/followers_visibility_test.py +34 -7
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/posts/post_deletion_integration_test.py +97 -7
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/posts/public/router_test.py +6 -16
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/users/authenticated/router_test.py +23 -95
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/users/public/router_test.py +27 -25
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/worker/account_deletion_test.py +3 -15
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/worker/aggregate_comment_stats_test.py +1 -4
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/worker/aggregate_post_stats_test.py +1 -4
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/worker/aggregate_user_stats_test.py +9 -30
- core_framework-2.3.0/tests/integration/worker/analytics_prune_test.py +77 -0
- core_framework-2.3.0/tests/integration/worker/analytics_refresh_test.py +109 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/worker/create_idempotency_retention_test.py +2 -12
- core_framework-2.3.0/tests/integration/worker/moderation_outbox_test.py +306 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/worker/mute_lift_test.py +19 -17
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/worker/stale_staging_sweep_test.py +6 -6
- core_framework-2.3.0/tests/integration/worker/user_outbox_test.py +374 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/worker/utils_test.py +1 -6
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/unit/application/events/event_service_test.py +4 -4
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/unit/core/pagination_test.py +7 -16
- core_framework-2.3.0/tests/unit/infrastructure/__init__.py +0 -0
- core_framework-2.3.0/tests/unit/infrastructure/media/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/uv.lock +1 -1
- core_framework-2.1.0/core_framework/application/events/README.md +0 -26
- core_framework-2.1.0/core_framework/application/posts/aggregation_service.py +0 -38
- core_framework-2.1.0/docs/domains/README.md +0 -17
- core_framework-2.1.0/docs/platform/event-outbox-design.md +0 -529
- core_framework-2.1.0/docs/todo.md +0 -10
- core_framework-2.1.0/tests/integration/api/_auth_helpers.py +0 -12
- {core_framework-2.1.0 → core_framework-2.3.0}/.agents/skills/fastapi +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.agents/skills/library-skills/.library-skills.json +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.agents/skills/library-skills/SKILL.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/api-reference-docs.mdc +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/api-validation.mdc +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/constants-final.mdc +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/database-triggers.mdc +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/docstrings.mdc +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/domain-input-guards.mdc +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/domain-services-and-adapters.mdc +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/flow-documentation.mdc +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/inline-raise-messages.mdc +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/integration-test-strategy.mdc +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/no-code-in-docs.mdc +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/postgres-config-conventions.mdc +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/repository-schema-isolation.mdc +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/structured-logging.mdc +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/skills/add-config/SKILL.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/skills/fastapi +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/skills/recommend-features/SKILL.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/skills/sync-host-rules/SKILL.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.cursor/skills/write-flow-doc/SKILL.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.dockerignore +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.github/workflows/dev-ci-cd.yaml +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.github/workflows/manual-deployment.yaml +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.github/workflows/publish-pypi.yml +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.gitignore +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.pre-commit-config.yaml +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/.python-version +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/LICENSE +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/README.md +0 -0
- {core_framework-2.1.0/alembic/comment → core_framework-2.3.0/alembic/analytics}/alembic/README +0 -0
- {core_framework-2.1.0/alembic/comment → core_framework-2.3.0/alembic/analytics}/alembic/script.py.mako +0 -0
- {core_framework-2.1.0/alembic/comment → core_framework-2.3.0/alembic/analytics}/alembic.ini +0 -0
- {core_framework-2.1.0/alembic/extension → core_framework-2.3.0/alembic/comment}/alembic/README +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/comment/alembic/env.py +0 -0
- {core_framework-2.1.0/alembic/extension → core_framework-2.3.0/alembic/comment}/alembic/script.py.mako +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/comment/alembic/versions/comment_add_comment_attachments.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/comment/alembic/versions/comment_add_comment_create_idempotency.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/comment/alembic/versions/comment_add_comment_create_idempotency_created_at_index.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/comment/alembic/versions/comment_add_comment_mentions.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/comment/alembic/versions/comment_tombstone_status_model.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/comment/alembic/versions/v1_comment_init_baseline.py +0 -0
- {core_framework-2.1.0/alembic/media → core_framework-2.3.0/alembic/comment}/alembic.ini +0 -0
- {core_framework-2.1.0/alembic/media → core_framework-2.3.0/alembic/extension}/alembic/README +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/extension/alembic/env.py +0 -0
- {core_framework-2.1.0/alembic/media → core_framework-2.3.0/alembic/extension}/alembic/script.py.mako +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/extension/alembic/versions/v1_ext_init_baseline.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/extension/alembic.ini +0 -0
- {core_framework-2.1.0/alembic/moderation → core_framework-2.3.0/alembic/media}/alembic/README +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/media/alembic/env.py +0 -0
- {core_framework-2.1.0/alembic/moderation → core_framework-2.3.0/alembic/media}/alembic/script.py.mako +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/media/alembic/versions/media_add_attachment_staging_registry.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/media/alembic/versions/media_add_banner_staging_and_lease.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/media/alembic/versions/media_attachment_staging_registry_published_at.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/media/alembic/versions/v1_media_init_baseline.py +0 -0
- {core_framework-2.1.0/alembic/notification → core_framework-2.3.0/alembic/media}/alembic.ini +0 -0
- {core_framework-2.1.0/alembic/notification → core_framework-2.3.0/alembic/moderation}/alembic/README +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/moderation/alembic/env.py +0 -0
- {core_framework-2.1.0/alembic/notification → core_framework-2.3.0/alembic/moderation}/alembic/script.py.mako +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/moderation/alembic/versions/v1_mod_init_baseline.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/moderation/alembic.ini +0 -0
- {core_framework-2.1.0/alembic/post → core_framework-2.3.0/alembic/notification}/alembic/README +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/notification/alembic/env.py +0 -0
- {core_framework-2.1.0/alembic/post → core_framework-2.3.0/alembic/notification}/alembic/script.py.mako +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/notification/alembic/versions/v1_notif_init_baseline.py +0 -0
- {core_framework-2.1.0/alembic/user → core_framework-2.3.0/alembic/post}/alembic/README +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/post/alembic/env.py +0 -0
- {core_framework-2.1.0/alembic/user → core_framework-2.3.0/alembic/post}/alembic/script.py.mako +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/post/alembic/versions/post_add_post_attachments.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/post/alembic/versions/post_add_post_create_idempotency.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/post/alembic/versions/post_add_post_create_idempotency_created_at_index.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/post/alembic/versions/post_add_post_mentions.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/post/alembic/versions/post_tombstone_status_model.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/post/alembic/versions/v1_post_init_baseline.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/post/alembic.ini +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/user/alembic/env.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/user/alembic/versions/user_add_avatar_ingest_sequences.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/user/alembic/versions/user_add_banner_ingest_sequences.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/user/alembic/versions/user_add_date_of_birth_to_profiles.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/user/alembic/versions/user_reserve_sentinel_user_ids.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/user/alembic/versions/v1_user_init_baseline.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/alembic/user/alembic.ini +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/admin/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/admin/cache/schemas.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/admin/comments/schemas.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/admin/moderation/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/admin/moderation/schemas.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/admin/posts/schemas.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/admin/users/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/attachments/router.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/attachments/schemas.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/auth/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/auth/router.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/auth/schemas.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/comments/authenticated/mappers.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/comments/public/router.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/comments/public/schemas.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/comments/router.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/comments/schemas.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/constants.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/dependencies.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/idempotency/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/idempotency/dependencies.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/notifications/authenticated/schemas.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/notifications/router.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/posts/authenticated/mappers.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/posts/public/router.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/posts/public/schemas.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/posts/router.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/posts/schemas.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/router.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/schemas.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/system/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/system/router.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/users/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/users/authenticated/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/users/authenticated/mappers.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/users/authenticated/schemas.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/users/mappers.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/users/public/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/users/public/router.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/users/public/schemas.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/users/router.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/api/users/shared/schemas.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/auth/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/auth/access_service.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/auth/auth_service.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/auth/models.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/cache/invalidate_service.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/cache/models.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/comments/aggregation_service.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/comments/cleanup_service.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/media/attachment_read.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/media/read_url_config.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/moderation/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/moderation/report_service.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/moderation/scheduled_service.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/notifications/README.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/notifications/enums.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/notifications/notification_service.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/posts/admin_service.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/posts/cleanup_service.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/shared/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/shared/user_agent.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/shared/worker_jobs.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/users/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/users/aggregation_service.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/users/deletion_audit.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/users/enums.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/application/users/scheduled_service.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/asgi.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/constants.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/core/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/core/cache.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/core/context.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/core/exception_handlers/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/core/firebase.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/core/http_client.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/core/observability.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/core/redis.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/auth/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/auth/dependencies.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/auth/exceptions.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/auth/models.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/auth/ports/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/auth/ports/auth.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/comment/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/comment/constants.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/comment/dependencies.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/comment/enums.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/comment/exceptions.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/comment/models.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/comment/outcomes.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/exceptions.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/README.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/attachment/components/staging.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/attachment/components/variant_encoder.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/attachment/constants.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/attachment/exceptions.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/attachment/models.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/attachment/ports/staging.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/attachment/ports/variant_encoder.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/avatar/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/avatar/components/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/avatar/components/finals_publisher.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/avatar/components/staging.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/avatar/components/variant_encoder.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/avatar/constants.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/avatar/exceptions.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/avatar/models.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/avatar/ports/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/avatar/ports/finals_publisher.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/avatar/ports/staging.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/avatar/ports/variant_encoder.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/banner/components/finals_publisher.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/banner/components/staging.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/banner/components/variant_encoder.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/banner/constants.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/banner/exceptions.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/banner/models.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/banner/ports/finals_publisher.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/banner/ports/staging.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/banner/ports/variant_encoder.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/dependencies.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/shared/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/shared/constants.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/shared/exceptions.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/shared/outcomes.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/shared/service.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/media/shared/utils.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/moderation/README.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/moderation/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/moderation/enums.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/moderation/exceptions.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/moderation/models.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/moderation/outcomes.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/notification/README.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/notification/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/notification/dependencies.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/notification/enums.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/notification/exceptions.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/notification/models.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/notification/repository.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/notification/service.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/post/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/post/constants.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/post/dependencies.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/post/enums.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/post/exceptions.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/post/outcomes.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/user/README.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/user/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/user/constants.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/user/enums.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/user/exceptions.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/user/models.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/domains/user/utils.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/infrastructure/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/infrastructure/firebase/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/infrastructure/firebase/firebase_auth_adapter.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/infrastructure/media/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/infrastructure/media/attachment_staging_display_geometry.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/infrastructure/media/local_attachment_staging_adapter.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/infrastructure/media/local_banner_staging_adapter.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/infrastructure/media/local_staging_adapter.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/infrastructure/media/pillow_attachment_variant_encoder_adapter.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/infrastructure/media/pillow_avatar_variant_encoder_adapter.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/infrastructure/media/pillow_banner_variant_encoder_adapter.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/infrastructure/media/pillow_staging_image.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/main.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/migrate_cli.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/testing/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/testing/config.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/testing/firebase.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/testing/hookspecs.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/testing/httpx_test_client.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/testing/migrations.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/testing/plugin.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/schedules/schedule_aggregate_comment_stats.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/schedules/schedule_aggregate_post_stats.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/schedules/schedule_aggregate_user_stats.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/schedules/schedule_expired_account_deletions.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/schedules/schedule_expired_mute_lifts.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/schedules/schedule_purge_expired_create_idempotency_keys.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/schedules/schedule_sweep_stale_attachment_staging.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/tasks/delete_attachment_finals.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/tasks/delete_superseded_avatar_finals.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/tasks/delete_superseded_banner_finals.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/tasks/process_account_deletion.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/tasks/process_admin_user_deletion.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/tasks/process_aggregate_comment_stats.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/tasks/process_aggregate_post_stats.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/tasks/process_aggregate_user_stats.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/tasks/process_banner_asset_variants.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/tasks/process_cache_pattern_invalidation.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/tasks/process_destructive_comment_delete.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/tasks/process_destructive_post_delete.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/tasks/process_media_asset_variants.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/tasks/process_mute_lift.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/tasks/process_user_content_deletion.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/core_framework/worker/worker_context.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docker-compose.dev.yaml +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docker-compose.yml +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/dockerfile +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/README.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/deployments/README.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/deployments/edge-upload-limits.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/deployments/examples/Caddyfile.example +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/deployments/examples/image-server-known-hosts.example +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/deployments/guides/app-server-deploy-ssh.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/deployments/guides/image-server-ssh.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/deployments/guides/postgres-setup.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/deployments/guides/redis-setup.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/deployments/guides/ubuntu-server-setup.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/deployments/release-playbook.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/auth/registration.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/comments/comment_report.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/comments/comment_stats_aggregation.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/comments/create_comment.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/comments/delete_comment.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/comments/edit_comment.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/comments/retrieve_comments.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/media/upload_pipeline.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/mentions/mentions_in_content.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/moderation/internal_notes.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/moderation/moderator_actions.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/moderation/reports.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/posts/hashtag_discovery.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/posts/post_visibility.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/users/account.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/users/avatar.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/users/banner.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/users/change_history.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/users/my_posts_and_comments.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/users/preferences.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/users/profile.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/domains/users/public_profile.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/library/README.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/library/core-framework-migration.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/library/testing-plugin-design.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/platform/create-idempotency-design.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/platform/database-triggers.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/platform/domain-services-and-adapters.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/platform/layers-and-boundaries.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/platform/media-storage-topology.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/docs/platform/patch-update-typed-payload.md +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/firebase_config.example.json +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/conftest.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/e2e/_canaries.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/e2e/_media.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/e2e/_settings.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/e2e/_upload_payloads.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/e2e/media/pipeline_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/e2e/smoke/canary_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/e2e/smoke/media_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/e2e/smoke/system_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/_http_error_details.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/_destructive_delete_helpers.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/_http_helpers.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/_user_deletion_helpers.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/_user_helpers.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/admin/__init__.py +0 -0
- {core_framework-2.1.0/tests/integration/api/admin/cache → core_framework-2.3.0/tests/integration/api/admin/analytics}/__init__.py +0 -0
- {core_framework-2.1.0/tests/integration/api/admin/comments → core_framework-2.3.0/tests/integration/api/admin/cache}/__init__.py +0 -0
- {core_framework-2.1.0/tests/integration/api/admin/moderation → core_framework-2.3.0/tests/integration/api/admin/comments}/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/admin/comments/router_test.py +0 -0
- {core_framework-2.1.0/tests/integration/api/admin/posts → core_framework-2.3.0/tests/integration/api/admin/moderation}/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/admin/moderation/_auth_cases.py +0 -0
- {core_framework-2.1.0/tests/integration/api/admin/users → core_framework-2.3.0/tests/integration/api/admin/outbox}/__init__.py +0 -0
- {core_framework-2.1.0/tests/integration/api/auth → core_framework-2.3.0/tests/integration/api/admin/posts}/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/admin/posts/router_test.py +0 -0
- {core_framework-2.1.0/tests/integration/api/comments → core_framework-2.3.0/tests/integration/api/admin/users}/__init__.py +0 -0
- {core_framework-2.1.0/tests/integration/api/comments/authenticated → core_framework-2.3.0/tests/integration/api/auth}/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/auth/router_test.py +0 -0
- {core_framework-2.1.0/tests/integration/api/comments/public → core_framework-2.3.0/tests/integration/api/comments}/__init__.py +0 -0
- {core_framework-2.1.0/tests/integration/api/notifications → core_framework-2.3.0/tests/integration/api/comments/authenticated}/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/comments/comment_attachments_integration_test.py +0 -0
- {core_framework-2.1.0/tests/integration/api/posts → core_framework-2.3.0/tests/integration/api/comments/public}/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/media/ingest_contract_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/mentions/mentions_integration_test.py +0 -0
- {core_framework-2.1.0/tests/integration/api/posts/public → core_framework-2.3.0/tests/integration/api/notifications}/__init__.py +0 -0
- {core_framework-2.1.0/tests/integration/api/system → core_framework-2.3.0/tests/integration/api/posts}/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/posts/comment_count_aggregation_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/posts/post_attachments_integration_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/posts/post_stats_dirty_marking_test.py +0 -0
- {core_framework-2.1.0/tests/integration/api/users → core_framework-2.3.0/tests/integration/api/posts/public}/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/public_read_by_id_unavailable_detail_test.py +0 -0
- {core_framework-2.1.0/tests/integration/api/users/authenticated → core_framework-2.3.0/tests/integration/api/system}/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/system/router_test.py +0 -0
- {core_framework-2.1.0/tests/integration/api/users/public → core_framework-2.3.0/tests/integration/api/users}/__init__.py +0 -0
- {core_framework-2.1.0/tests/integration/worker → core_framework-2.3.0/tests/integration/api/users/authenticated}/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/users/authenticated/avatar_upload_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/users/authenticated/banner_upload_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/users/deletion_audit_integration_test.py +0 -0
- {core_framework-2.1.0/tests/unit → core_framework-2.3.0/tests/integration/api/users/public}/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/api/users/user_deletion_integration_test.py +0 -0
- {core_framework-2.1.0/tests/unit/application/comments → core_framework-2.3.0/tests/integration/worker}/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/worker/_media_upload_helpers.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/worker/attachment_upload_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/worker/avatar_upload_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/worker/banner_upload_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/integration/worker/conftest.py +0 -0
- {core_framework-2.1.0/tests/unit/domains → core_framework-2.3.0/tests/unit}/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/unit/api/comments/authenticated/comment_request_schemas_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/unit/api/dedupe_preserving_order_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/unit/api/posts/authenticated/post_update_mapper_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/unit/api/users/profile_update_mapper_test.py +0 -0
- {core_framework-2.1.0/tests/unit/domains/comment → core_framework-2.3.0/tests/unit/application/comments}/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/unit/application/notifications/inbox_service_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/unit/application/users/deletion_audit_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/unit/core/bundled_alembic_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/unit/core/migrate_cli_test.py +0 -0
- {core_framework-2.1.0/tests/unit/infrastructure → core_framework-2.3.0/tests/unit/domains}/__init__.py +0 -0
- {core_framework-2.1.0/tests/unit/infrastructure/media → core_framework-2.3.0/tests/unit/domains/comment}/__init__.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/unit/domains/post/models_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/unit/domains/user/service_test.py +0 -0
- {core_framework-2.1.0 → core_framework-2.3.0}/tests/unit/infrastructure/media/ssh_finals_publisher_adapter_test.py +0 -0
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Alembic revision id format
|
|
2
|
+
description: Alembic migrations — revision id format and index DDL conventions
|
|
3
3
|
globs: alembic/**/alembic/versions/*.py
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# Alembic
|
|
7
|
+
# Alembic migrations
|
|
8
|
+
|
|
9
|
+
## Revision IDs
|
|
8
10
|
|
|
9
11
|
`alembic_version.version_num` is a **32-character** string. Revision ids must stay well under that limit.
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
### New revisions (required)
|
|
12
14
|
|
|
13
15
|
- Set `revision` to a **12-character lowercase hex** string (Alembic default), e.g. `a56bc6b7f799`.
|
|
14
16
|
- Generate with `python -c "import secrets; print(secrets.token_hex(6))"` or `alembic revision`.
|
|
@@ -30,10 +32,29 @@ revision: str = "120bf6be81eb"
|
|
|
30
32
|
revision: str = "post_tombstone_status_model"
|
|
31
33
|
```
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
### Exceptions
|
|
34
36
|
|
|
35
37
|
- Collapsed **`v1_*_init`** baseline revisions keep their stable names (`v1_post_init`, etc.) — do not rename shipped baselines.
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
### Do not rename shipped revision ids
|
|
38
40
|
|
|
39
41
|
If a revision may already be applied in any environment, **do not** change its `revision` string. Add a forward migration instead.
|
|
42
|
+
|
|
43
|
+
## Index DDL
|
|
44
|
+
|
|
45
|
+
### New index migrations (required)
|
|
46
|
+
|
|
47
|
+
In **`upgrade()`**, create indexes with **`IF NOT EXISTS`** so migrations succeed when an index was already applied manually in production:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
op.execute("""
|
|
51
|
+
create index if not exists idx_example_created_at
|
|
52
|
+
on example_table (created_at);
|
|
53
|
+
""")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Use **`drop index if exists`** in **`downgrade()`**.
|
|
57
|
+
|
|
58
|
+
### Do not retrofit index DDL
|
|
59
|
+
|
|
60
|
+
Do **not** edit shipped migrations only to add `if not exists` — those revisions may already be applied. New forward migrations follow this rule; legacy `create index` without `if not exists` stays as-is.
|
|
@@ -6,7 +6,7 @@ globs:
|
|
|
6
6
|
|
|
7
7
|
# API layer (core-framework)
|
|
8
8
|
|
|
9
|
-
**Framework patterns:** use the **fastapi** skill (`.
|
|
9
|
+
**Framework patterns:** use the **fastapi** skill (`.agents/skills/fastapi/`) — `Annotated`, dependencies, routers, Pydantic v2, CLI.
|
|
10
10
|
|
|
11
11
|
**Validation:** **`api-validation.mdc`**. **Errors:** **`error-boundary.mdc`**. **Domain imports:** **`domain-imports.mdc`**.
|
|
12
12
|
|
|
@@ -18,7 +18,7 @@ Application services orchestrate use cases shared by API and workers. They stay
|
|
|
18
18
|
|
|
19
19
|
- Return plain Python/domain values: `dict`, `list[dict]`, dataclasses, primitives, `None`, or empty collections.
|
|
20
20
|
- Do not return API/Pydantic `BaseModel` instances.
|
|
21
|
-
- Signal HTTP-meaningful failures with **`application.shared.exceptions`** or plain outcomes (`None`, empty), never typed domain exceptions.
|
|
21
|
+
- Signal HTTP-meaningful failures with **`application.shared.exceptions`** or plain outcomes (`None`, empty), never typed domain exceptions. Full boundary contract: **`error-boundary.mdc`**.
|
|
22
22
|
|
|
23
23
|
Use a domain dataclass when one domain row matches the response shape; use a `dict` when the payload is composed from several sources.
|
|
24
24
|
|
|
@@ -10,6 +10,7 @@ alwaysApply: true
|
|
|
10
10
|
- State assumptions; ask when unclear. Prefer the simplest correct solution.
|
|
11
11
|
- Touch only what the task requires. Match existing style. Remove orphans your edits create.
|
|
12
12
|
- Define verifiable success criteria for non-trivial work.
|
|
13
|
+
- **Inline small single-use helpers** — do not extract a private function used once and only a few lines (identifier quoting, a short SQL fragment, pass-through wrappers). Extract when the logic is **reused**, **~15+ lines / non-obvious branching**, or a scoped rule requires sharing (e.g. normal + `*_strong` repository reads in **`strong-reads.mdc`**). Do not repo-wide refactor unrelated existing helpers.
|
|
13
14
|
|
|
14
15
|
## Tech stack
|
|
15
16
|
|
|
@@ -25,7 +26,12 @@ Dependencies point inward: API/worker → application → domain. Never reverse.
|
|
|
25
26
|
- **Application** — orchestration shared by API + worker; no FastAPI types.
|
|
26
27
|
- **Domain** — models, repositories (Postgres only), services. No API/worker/application imports except wiring in `dependencies.py`.
|
|
27
28
|
|
|
28
|
-
Path-scoped rules under `.cursor/rules/` add detail (API, domains, docs, etc.). For FastAPI/Pydantic work in `core_framework/api/`, use the **fastapi** skill (`.
|
|
29
|
+
Path-scoped rules under `.cursor/rules/` add detail (API, domains, docs, etc.). For FastAPI/Pydantic work in `core_framework/api/`, use the **fastapi** skill (`.agents/skills/fastapi/`).
|
|
30
|
+
|
|
31
|
+
## Agent skills
|
|
32
|
+
|
|
33
|
+
- Package skills (for example **fastapi**) are Library Skills-managed symlinks under **`.agents/skills/<name>/`**. Rules and hand-authored skills reference that path, not a copied tree under **`.cursor/skills/`**.
|
|
34
|
+
- **`.cursor/skills/<name>`** may symlink to **`.agents/skills/<name>`** for Cursor; Library Skills repairs **`.agents/skills`** links when packages move. After **`uv sync`** or a dependency upgrade, run **`uvx library-skills --yes`** if symlinks are stale.
|
|
29
35
|
|
|
30
36
|
## Host applications
|
|
31
37
|
|
|
@@ -34,7 +34,7 @@ Domain layer names must describe domain concepts and data semantics, not who cal
|
|
|
34
34
|
- `tombstone_post` ✅
|
|
35
35
|
- `detach_post_authors_by_author_id` ✅
|
|
36
36
|
- Prefer naming by data shape or business rule, not persona:
|
|
37
|
-
- `
|
|
37
|
+
- `select_post_with_metadata_by_id_if_exists` ✅
|
|
38
38
|
- `retrieve_user_for_detail` ✅
|
|
39
39
|
- Keep caller-specific naming in API/application layers only.
|
|
40
40
|
|
|
@@ -16,7 +16,7 @@ When importing from **`core_framework/domains/*`** in API or application code:
|
|
|
16
16
|
|
|
17
17
|
## Prohibited
|
|
18
18
|
|
|
19
|
-
- Typed or base **domain exceptions** in API/application — use **`application.shared.exceptions`**.
|
|
19
|
+
- Typed or base **domain exceptions** in API/application — use **`application.shared.exceptions`**. Full policy: **`error-boundary.mdc`**.
|
|
20
20
|
- Direct imports from internal modules (`models`, `enums`, `repository`, `service`, `exceptions`).
|
|
21
21
|
|
|
22
22
|
```python
|
|
@@ -41,4 +41,4 @@ To see what a domain exports, check its **`__init__.py`** **`__all__`**.
|
|
|
41
41
|
2. Raise an **application** exception when HTTP must signal failure.
|
|
42
42
|
3. Extend the domain service API to absorb typed exceptions internally.
|
|
43
43
|
|
|
44
|
-
Application code never imports repositories. Exception boundary
|
|
44
|
+
Application code never imports repositories. Exception boundary: **`error-boundary.mdc`**.
|
{core_framework-2.1.0 → core_framework-2.3.0}/.cursor/rules/domain-repository-exceptions.mdc
RENAMED
|
@@ -6,15 +6,18 @@ globs:
|
|
|
6
6
|
|
|
7
7
|
# Domain: repository exceptions vs service
|
|
8
8
|
|
|
9
|
+
Canonical HTTP/application boundary: **`error-boundary.mdc`**. Application and API must not import or handle typed domain exceptions.
|
|
10
|
+
|
|
9
11
|
## Thin service + single persistence step
|
|
10
12
|
|
|
11
|
-
When a **`Service`** method in **`core_framework/domains/*/service.py`** only performs **shared input guards** (e.g. **`require_*`**, **`validate_pagination_limit`**) **and** delegates to **one** repository call with **no branching** afterward,
|
|
13
|
+
When a **`Service`** method in **`core_framework/domains/*/service.py`** only performs **shared input guards** (e.g. **`require_*`**, **`validate_pagination_limit`**) **and** delegates to **one** repository call with **no branching** afterward, the repository may raise typed domain exceptions for persistence error outcomes (empty `RETURNING`, unexpected row absence on insert).
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
That propagation is **intra-domain only**. Methods that **application** or **workers** call must **not** leak typed exceptions outward — absorb them inside the service (return **`None`**, outcomes, or **`bool`**) or branch before re-raising. See **`error-boundary.mdc`**.
|
|
14
16
|
|
|
15
17
|
## Prefer service-side translation when
|
|
16
18
|
|
|
17
19
|
- The service **composes multiple** repository calls, **branches** on outcomes, or maps the same SQL outcome to **different domain errors**.
|
|
20
|
+
- The method is an **application-callable** entrypoint (anything imported via **`dependencies.py`** and used from **`core_framework/application/`** or **`core_framework/api/`**).
|
|
18
21
|
- Keeping the repository purely “data shaped” (**`Optional`** / **`bool`**) materially improves reuse or clarity for **multiple** service entrypoints calling the same repository method.
|
|
19
22
|
|
|
20
23
|
Existing domains may mix both styles; use this guide for **new work** and when **refactoring** a call site for clarity.
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
description: HTTP error boundaries — application vs domain exceptions, handlers, anti-enumeration
|
|
3
3
|
globs:
|
|
4
4
|
- core_framework/core/exception_handlers/**/*.py
|
|
5
|
-
- core_framework/application
|
|
5
|
+
- core_framework/application/**/*.py
|
|
6
6
|
- core_framework/api/**/router.py
|
|
7
|
+
- core_framework/api/**/schemas.py
|
|
7
8
|
- core_framework/domains/**/exceptions.py
|
|
8
9
|
---
|
|
9
10
|
|
|
@@ -15,7 +16,7 @@ Platform contract: **`docs/platform/client-error-visibility.md`**. Domain import
|
|
|
15
16
|
|
|
16
17
|
| Layer | Outward errors | HTTP |
|
|
17
18
|
|-------|----------------|------|
|
|
18
|
-
| **Application** | **`application.shared.exceptions`** | Handlers map type → status + `detail` |
|
|
19
|
+
| **Application** | **`application.shared.exceptions`**, **`InvalidPageTokenError`** | Handlers map type → status + `detail` |
|
|
19
20
|
| **Domain typed** | Internal — not imported by API/application | No typed handlers |
|
|
20
21
|
| **Domain `BaseXxxException`** | Handler catch-all only | **500** if leaked |
|
|
21
22
|
| **API** | Pydantic | **422** |
|
|
@@ -30,7 +31,7 @@ Platform contract: **`docs/platform/client-error-visibility.md`**. Domain import
|
|
|
30
31
|
## Handler registration
|
|
31
32
|
|
|
32
33
|
- Use **`register_exception_handler`** from **`core_framework.core.exception_handlers.common`** — not raw **`app.add_exception_handler`** elsewhere.
|
|
33
|
-
- **`setup.py`**: application types (specific before any base), **`DomainValidationError`**, one **`BaseDomainException`** leak handler, then **`Exception`** unmapped.
|
|
34
|
+
- Register handlers in **`core_framework/core/exception_handlers/setup.py`** and **`application.py`**: application types (specific before any base), **`InvalidPageTokenError`**, **`DomainValidationError`**, one **`BaseDomainException`** leak handler, then **`Exception`** unmapped.
|
|
34
35
|
- No per-domain handler modules for typed exceptions. Handlers translate only — no business logic or repository calls.
|
|
35
36
|
- Do not log **error** for expected application 4xx; **`DomainValidationError`** at **warning**/**info**.
|
|
36
37
|
|
|
@@ -18,7 +18,7 @@ Dependencies point inward: **API/worker → application → domain**. Never reve
|
|
|
18
18
|
|
|
19
19
|
## Cross-layer rules
|
|
20
20
|
|
|
21
|
-
- Worker code must not import `core_framework.domains` directly
|
|
21
|
+
- Worker code must not import `core_framework.domains` directly; call **application** services from worker tasks and schedules.
|
|
22
22
|
- Domain code imports global infrastructure only in `dependencies.py`.
|
|
23
23
|
- Repositories are Postgres-only and must not query other domains' schemas. See **`repository-schema-isolation`**.
|
|
24
24
|
- Non-SQL I/O uses domain ports/components plus infrastructure adapters. See **`domain-services-and-adapters`**.
|
|
@@ -4,7 +4,7 @@ globs:
|
|
|
4
4
|
- core_framework/domains/**/repository.py
|
|
5
5
|
- core_framework/domains/**/service.py
|
|
6
6
|
- core_framework/domains/**/dependencies.py
|
|
7
|
-
- core_framework/application
|
|
7
|
+
- core_framework/application/**/*.py
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Strong reads
|
|
@@ -13,6 +13,7 @@ globs:
|
|
|
13
13
|
|
|
14
14
|
- **Read-after-write** in the same HTTP request or worker job → **strong** read (required). Refetch with `strong_read_database`, `*_strong` repository methods, or service accessors that call them.
|
|
15
15
|
- **Decision-critical guards** where stale data can cause incorrect branching → **strong** read (required). These are reads performed to decide a branch (for example pre-write existence, role, or state checks that determine whether a write runs or which outcome is returned), even when there is no immediate prior write in the flow. See the "Read Consistency (Strong vs Normal Reads)" ADR.
|
|
16
|
+
- **Accepted exception:** **`check_not_banned`** / **`get_user_detail`** (`USER_DETAIL` cache + normal reads) — ban visibility is eventually consistent by design. See **`docs/platform/architecture-decisions.md`** → *Ban enforcement via cached `USER_DETAIL` is eventually consistent*. Do not flag as a strong-read defect unless that ADR is updated.
|
|
16
17
|
- **All other strong-read usage** (e.g., list/browse freshness) → opt-in unless the user explicitly requested it in the task.
|
|
17
18
|
- If a flow writes then reads and only a normal-read API exists, add or extend the `*_strong` path and call it from that flow.
|
|
18
19
|
- Repositories accept `strong_read_database: Postgres` on `__init__` for wiring symmetry even when unused.
|
|
@@ -21,9 +22,8 @@ globs:
|
|
|
21
22
|
|
|
22
23
|
- Normal reads: explicit methods (e.g. `select_appeal`) using `read_database`.
|
|
23
24
|
- Strong reads: separate methods only where needed (e.g. `select_appeal_strong`) using `strong_read_database`.
|
|
24
|
-
- Share one private helper that accepts a database handle — do not duplicate SQL or mapping.
|
|
25
|
-
-
|
|
26
|
-
- Service accessors that call strong repository methods should also include `_strong`.
|
|
25
|
+
- Share one private helper that accepts a database handle when **both** normal and strong public methods exist — do not duplicate SQL or mapping. If only a `*_strong` method exists today, keep SQL inline in that method (constitution → inline small single-use helpers).
|
|
26
|
+
- Service accessors that call strong repository methods should use **`retrieve_*_strong`** naming (repositories keep **`select_*_strong`**).
|
|
27
27
|
|
|
28
28
|
```python
|
|
29
29
|
# Preferred
|
|
@@ -192,7 +192,7 @@ Worker `startup` and API `init_app` already call `configure_application_dependen
|
|
|
192
192
|
|
|
193
193
|
Do this in a **separate step/PR** when you have DDL to apply.
|
|
194
194
|
|
|
195
|
-
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-
|
|
195
|
+
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-migrations`**); put the descriptive slug in the filename only.
|
|
196
196
|
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).
|
|
197
197
|
1. `make alembic` and deploy’s `cf-alembic` step pick up the new domain automatically — no makefile or `_deploy.yml` edits needed.
|
|
198
198
|
|
|
@@ -25,7 +25,7 @@ Do **not** flag documented accepted trade-offs in **`docs/platform/architecture-
|
|
|
25
25
|
## Quick checks
|
|
26
26
|
|
|
27
27
|
- Application services: no HTTP types, no domain exceptions, no Pydantic return types, one-line docstrings.
|
|
28
|
-
- API: thin routers, validation in schemas, orchestration in application — **fastapi** skill + **`api-layer`** / **`api-validation`** / **`error-boundary`** rules.
|
|
28
|
+
- API: thin routers, validation in schemas, orchestration in application — **fastapi** skill (`.agents/skills/fastapi/`) + **`api-layer`** / **`api-validation`** / **`error-boundary`** rules.
|
|
29
29
|
- Domain: imports only via **`__init__.py`** / **`dependencies`** from outer layers; no caller-context names (**`domain-caller-context`**).
|
|
30
30
|
- Read-after-write refetches use **`*_strong`** where applicable.
|
|
31
31
|
|
|
@@ -24,7 +24,7 @@ Implement **one step at a time**. Stop after each step and wait for user review.
|
|
|
24
24
|
After the user accepts database changes (or if there were none):
|
|
25
25
|
|
|
26
26
|
- **Only** implement the API layer: schemas and router.
|
|
27
|
-
- Follow the **fastapi** skill and **`api-layer`** / **`api-validation`** / **`error-boundary`** rules.
|
|
27
|
+
- Follow the **fastapi** skill (`.agents/skills/fastapi/`) and **`api-layer`** / **`api-validation`** / **`error-boundary`** rules.
|
|
28
28
|
- **No** changes in domain, service, or application layers.
|
|
29
29
|
- Include request/response schemas, error handling, and HTTP-related logic.
|
|
30
30
|
- If the feature is not yet implemented, return 501 Not Implemented from the route.
|
|
@@ -55,10 +55,13 @@ jobs:
|
|
|
55
55
|
IMAGE_SERVER_PORT: ${{ vars.IMAGE_SERVER_PORT }}
|
|
56
56
|
IMAGE_SERVER_USER: ${{ vars.IMAGE_SERVER_USER }}
|
|
57
57
|
LOGFIRE_TOKEN: ${{ secrets.LOGFIRE_TOKEN }}
|
|
58
|
+
OBSERVABILITY_ENABLED: ${{ vars.OBSERVABILITY_ENABLED }}
|
|
58
59
|
PROFILING_KEY: ${{ vars.PROFILING_KEY }}
|
|
59
60
|
PROFILING_ENABLED: ${{ vars.PROFILING_ENABLED }}
|
|
60
61
|
LOG_LEVEL: ${{ vars.LOG_LEVEL }}
|
|
61
62
|
CORS_ALLOWED_ORIGINS: ${{ vars.CORS_ALLOWED_ORIGINS }}
|
|
63
|
+
RETENTION_POST_VIEWS_DAYS: ${{ vars.RETENTION_POST_VIEWS_DAYS }}
|
|
64
|
+
ANALYTICS_REFRESH_INTERVAL_SECONDS: ${{ vars.ANALYTICS_REFRESH_INTERVAL_SECONDS }}
|
|
62
65
|
run: |
|
|
63
66
|
set -euo pipefail
|
|
64
67
|
|
|
@@ -32,19 +32,14 @@ jobs:
|
|
|
32
32
|
run: uv run ruff check
|
|
33
33
|
|
|
34
34
|
- name: Install Firebase CLI
|
|
35
|
-
run: npm install -g firebase-tools
|
|
36
|
-
|
|
37
|
-
- name: Start Firebase Emulator
|
|
38
|
-
run: |
|
|
39
|
-
firebase emulators:start --only auth --project=test &
|
|
40
|
-
sleep 5
|
|
41
|
-
env:
|
|
42
|
-
FIREBASE_AUTH_EMULATOR_HOST: localhost:9099
|
|
35
|
+
run: npm install -g firebase-tools@15.20.0
|
|
43
36
|
|
|
44
37
|
- name: pytest
|
|
45
|
-
run:
|
|
38
|
+
run: |
|
|
39
|
+
firebase --version
|
|
40
|
+
uv run pytest tests/unit tests/integration -n 2
|
|
46
41
|
env:
|
|
47
|
-
FIREBASE_AUTH_EMULATOR_HOST:
|
|
42
|
+
FIREBASE_AUTH_EMULATOR_HOST: 127.0.0.1:9099
|
|
48
43
|
|
|
49
44
|
package-smoke:
|
|
50
45
|
runs-on: ubuntu-latest
|
|
@@ -4,6 +4,56 @@ Notable changes to **core-framework** (import **`core_framework`**). Format foll
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.3.0] - 2026-07-03
|
|
8
|
+
|
|
9
|
+
Analytics rollup domain (materialized daily activity counts), admin read API, and retention prune. Hosts must add **`[analytics]`** and **`schema_analytics`**, run **`cf-alembic`**, and run the worker for rollup refresh and daily-fact prune.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Analytics rollup (Phase 1):** `analytics` domain scaffold, `schema_analytics`, `[analytics].refresh_interval_seconds` (required on **`Settings`**; core **`config.toml`** ships **300**), Alembic tree with `daily_fact` table (PK only), and activity source registry shell. See **`docs/platform/analytics-rollup-design.md`**.
|
|
14
|
+
- **Analytics rollup (Phase 2):** `AnalyticsService.list_refreshable_days` and `is_final_bucket_refresh` (planner inputs), `refresh_daily_fact_bucket` (single-bucket executor), optional `refresh_all_open_buckets` for tests; transactional `replace_daily_fact_bucket`; admin read helpers.
|
|
15
|
+
- **Analytics rollup (Phase 3):** `post` / `created` activity source, planner (`enqueue_refresh_analytics_buckets`) and executor (`refresh_analytics_bucket`) application entrypoints, `schedule_refresh_analytics` + `process_refresh_analytics_bucket` worker (cron from `refresh_interval_seconds`).
|
|
16
|
+
- **Analytics rollup (Phase 4):** `comment` / `created`, `user` / `registered`, and `post` / `viewed` activity sources with domain UTC-day count accessors and registry registration.
|
|
17
|
+
- **Analytics rollup (Phase 5):** admin read API — `GET /admin/analytics/daily/range` and `GET /admin/analytics/daily/day` via `read_service` and Phase 2 domain read helpers.
|
|
18
|
+
- **Analytics rollup (Phase 6):** one-year `daily_fact` retention prune (`schedule_prune_analytics_daily_facts`, daily **05:10 UTC**); `idx_daily_fact_day` migration. Source-feed `created_at` indexes for refresh counts already exist on `posts`, `comments`, `users`, and `post_views`.
|
|
19
|
+
- **Analytics rollup (Phase 7):** integration tests for refresh planner/executor, admin read API, and retention prune.
|
|
20
|
+
|
|
21
|
+
### Breaking
|
|
22
|
+
|
|
23
|
+
- **Breaking (hosts — config):** Add **`[analytics]`** with **`refresh_interval_seconds`** (required on **`Settings`**; core **`config.toml`** ships **300**). Deploy variable **`ANALYTICS_REFRESH_INTERVAL_SECONDS`**. Add **`schema_analytics`** under **`[postgres_schemas]`** (default **`analytics`**).
|
|
24
|
+
- **Breaking (hosts — migration):** When upgrading to a release that includes analytics rollup, run **`cf-alembic`** so the **`analytics`** migration stream applies **`analytics_daily_fact`** (`analytics.daily_fact`) and **`analytics_daily_fact_day_index`** (`idx_daily_fact_day`). The worker must run **`schedule_refresh_analytics`** and **`schedule_prune_analytics_daily_facts`** (registered in core worker schedules) for rollup materialization and one-year retention prune.
|
|
25
|
+
|
|
26
|
+
## [2.2.0] - 2026-06-30
|
|
27
|
+
|
|
28
|
+
Transactional outbox (block, follow, restriction), post views retention, and incremental view-count aggregation. Hosts must update **`config.toml`**, run **`cf-alembic`**, and run the worker for mirror fanout and retention jobs.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **Post views retention:** Incremental **`view_count`** aggregation via **`post_stats.view_count_aggregated_up_to`** (high-water mark on **`post_views.created_at`**); daily worker **`schedule_prune_post_views`** (05:00 UTC) deletes aggregated rows older than **`retention.post_views_retention_days`** (default **90**). See **`docs/domains/posts/post_stats_aggregation.md`**.
|
|
33
|
+
- **Transactional outbox (block pilot):** `user.event_outbox`, worker poller/handler for **`user.block.relationship_changed`**, durable post/comment/notification block mirror fanout. See **`docs/platform/event-outbox-design.md`**.
|
|
34
|
+
- **Transactional outbox (follow, Phase 2):** **`user.follow.relationship_changed`** enqueued with truth changes; worker reconciles **`post.user_followers_lookup`** (outbox-only mirror path, matching block). Non-retryable handler errors dead-letter immediately (decision **#22**).
|
|
35
|
+
- **Transactional outbox (restriction, Phase 3):** **`moderation.restriction.relationship_changed`** enqueued on truth changes and no-op healing; worker reconciles post/comment/user/notification restriction lookup mirrors (outbox-only, matching block/follow). Poller E2E in **`tests/integration/worker/moderation_outbox_test.py`**. Run **`cf-alembic`** for **`moderation_add_event_outbox`**.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **Appeal approval retry:** Same-decision retries on already-approved appeals re-run `clear_user_restriction` instead of returning **400**, recovering when restriction removal failed after the appeal was persisted.
|
|
40
|
+
- **Worker staging sweeps:** Avatar and banner stale-staging cron schedules call **`sweep_*_staging_housekeeping`** application entrypoints instead of domain dependencies directly.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- **Post stats aggregation:** **`view_count`** is maintained incrementally from **`post_views`** since the last high-water mark instead of a full **`count(*)`** per run; tombstone resets clear **`view_count_aggregated_up_to`**.
|
|
45
|
+
|
|
46
|
+
### Breaking
|
|
47
|
+
|
|
48
|
+
- **Breaking (hosts — config):** When upgrading to a release that includes the outbox block pilot, update **`config.toml`** (and deploy templates) before startup:
|
|
49
|
+
- Rename **`[http-client]`** → **`[http_client]`** (field name on **`Settings`** is now **`http_client`** with no hyphen alias).
|
|
50
|
+
- Add **`[outbox]`** with **`min_connections`** and **`max_connections`** (defaults in core **`config.toml.template`**: **2** / **12**). The outbox pool is opened only by the **worker** process (and test workers via **`create_worker_settings_for_shared_runtime`**), not the API lifespan.
|
|
51
|
+
- Run **`cf-alembic`** so the **`user`** migration stream applies **`user_add_event_outbox`** (`"user".event_outbox`).
|
|
52
|
+
- Hosts that only run the API (no worker) still need **`[outbox]`** in config because **`Settings`** requires it; the API does not connect that pool.
|
|
53
|
+
- **Breaking (hosts — migration):** When upgrading to a release that includes restriction outbox (Phase 3), run **`cf-alembic`** so the **`moderation`** migration stream applies **`moderation_add_event_outbox`** (`moderation.event_outbox`). The worker must run **`schedule_poll_moderation_outbox`** (registered in core worker schedules) for durable restriction mirror fanout.
|
|
54
|
+
- **Breaking (hosts — config):** Add **`[retention]`** with **`post_views_retention_days`** (default **90**). Deploy variable **`RETENTION_POST_VIEWS_DAYS`**. Hosts that subclass **`Settings`** may extend **`RetentionConfig`** with additional retention keys (for example calculator views on Henry).
|
|
55
|
+
- **Breaking (hosts — migration):** When upgrading to a release that includes post views retention, run **`cf-alembic`** so the **`post`** migration stream applies **`post_post_views_retention_high_water_mark`** (`post_stats.view_count_aggregated_up_to`, **`post_views`** retention indexes, backfill). The worker must run **`schedule_prune_post_views`** (registered in core worker schedules) for age-based **`post_views`** pruning.
|
|
56
|
+
|
|
7
57
|
## [2.1.0] - 2026-06-25
|
|
8
58
|
|
|
9
59
|
Dependency and tooling refresh. No API, schema, or host config changes — bump the package and redeploy; no **`cf-alembic`** step.
|
|
@@ -349,4 +399,5 @@ First **SemVer-stable** release per **`docs/library/package-api.md`**.
|
|
|
349
399
|
[1.8.1]: https://github.com/NepNepFFXIV/core-framework/compare/v1.8.0...v1.8.1
|
|
350
400
|
[2.0.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.8.1...v2.0.0
|
|
351
401
|
[2.1.0]: https://github.com/NepNepFFXIV/core-framework/compare/v2.0.0...v2.1.0
|
|
352
|
-
[
|
|
402
|
+
[2.2.0]: https://github.com/NepNepFFXIV/core-framework/compare/v2.1.0...v2.2.0
|
|
403
|
+
[unreleased]: https://github.com/NepNepFFXIV/core-framework/compare/v2.2.0...HEAD
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: core-framework
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Core framework package (import as core_framework)
|
|
5
5
|
Project-URL: Homepage, https://github.com/NepNepFFXIV/core-framework
|
|
6
6
|
Project-URL: Repository, https://github.com/NepNepFFXIV/core-framework
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from logging.config import fileConfig
|
|
3
|
+
|
|
4
|
+
from sqlalchemy import pool, text
|
|
5
|
+
from sqlalchemy.engine import Connection
|
|
6
|
+
from sqlalchemy.ext.asyncio import async_engine_from_config
|
|
7
|
+
|
|
8
|
+
from alembic import context
|
|
9
|
+
from core_framework.core.settings import load_default_settings
|
|
10
|
+
|
|
11
|
+
settings = load_default_settings()
|
|
12
|
+
|
|
13
|
+
config = context.config
|
|
14
|
+
|
|
15
|
+
if config.config_file_name is not None:
|
|
16
|
+
fileConfig(config.config_file_name)
|
|
17
|
+
|
|
18
|
+
target_metadata = None
|
|
19
|
+
|
|
20
|
+
config.set_main_option("sqlalchemy.url", settings.write_postgres.alembic_postgres_url)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def run_migrations_offline() -> None:
|
|
24
|
+
"""Run migrations in 'offline' mode."""
|
|
25
|
+
url = config.get_main_option("sqlalchemy.url")
|
|
26
|
+
context.configure(
|
|
27
|
+
url=url,
|
|
28
|
+
target_metadata=target_metadata,
|
|
29
|
+
literal_binds=True,
|
|
30
|
+
dialect_opts={"paramstyle": "named"},
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
quoted_schema_name = f'"{settings.postgres_schemas.schema_analytics.replace('"', '""')}"'
|
|
34
|
+
|
|
35
|
+
with context.begin_transaction():
|
|
36
|
+
context.execute(text(f"set search_path to {quoted_schema_name}"))
|
|
37
|
+
context.run_migrations()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def do_run_migrations(connection: Connection) -> None:
|
|
41
|
+
quoted_schema_name = f'"{settings.postgres_schemas.schema_analytics.replace('"', '""')}"'
|
|
42
|
+
|
|
43
|
+
connection.execute(text(f"create schema if not exists {quoted_schema_name}"))
|
|
44
|
+
connection.execute(text(f"set search_path to {quoted_schema_name}"))
|
|
45
|
+
connection.commit()
|
|
46
|
+
|
|
47
|
+
context.configure(connection=connection, target_metadata=target_metadata)
|
|
48
|
+
|
|
49
|
+
with context.begin_transaction():
|
|
50
|
+
context.run_migrations()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def run_async_migrations() -> None:
|
|
54
|
+
"""Run migrations in 'online' mode."""
|
|
55
|
+
connectable = async_engine_from_config(
|
|
56
|
+
config.get_section(config.config_ini_section, {}),
|
|
57
|
+
prefix="sqlalchemy.",
|
|
58
|
+
poolclass=pool.NullPool,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
async with connectable.connect() as connection:
|
|
62
|
+
await connection.run_sync(do_run_migrations)
|
|
63
|
+
|
|
64
|
+
await connectable.dispose()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def run_migrations_online() -> None:
|
|
68
|
+
asyncio.run(run_async_migrations())
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if context.is_offline_mode():
|
|
72
|
+
run_migrations_offline()
|
|
73
|
+
else:
|
|
74
|
+
run_migrations_online()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""analytics daily_fact
|
|
2
|
+
|
|
3
|
+
Revision ID: 5b9ac98bb150
|
|
4
|
+
Revises:
|
|
5
|
+
Create Date: 2026-07-01 12:00:00.000000
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
|
|
11
|
+
from alembic import op
|
|
12
|
+
|
|
13
|
+
revision: str = "5b9ac98bb150"
|
|
14
|
+
down_revision: str | Sequence[str] | None = None
|
|
15
|
+
branch_labels: str | Sequence[str] | None = None
|
|
16
|
+
depends_on: str | Sequence[str] | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade() -> None:
|
|
20
|
+
op.execute("""
|
|
21
|
+
create table daily_fact (
|
|
22
|
+
subject_type text not null,
|
|
23
|
+
activity_type text not null,
|
|
24
|
+
day date not null,
|
|
25
|
+
kind text not null default '',
|
|
26
|
+
count bigint not null default 0,
|
|
27
|
+
primary key (subject_type, activity_type, day, kind)
|
|
28
|
+
);
|
|
29
|
+
""")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def downgrade() -> None:
|
|
33
|
+
op.execute("drop table if exists daily_fact;")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""analytics daily_fact day index for retention prune
|
|
2
|
+
|
|
3
|
+
Revision ID: 7f2a9c1d4e8b
|
|
4
|
+
Revises: 5b9ac98bb150
|
|
5
|
+
Create Date: 2026-07-02 12:00:00.000000
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
|
|
11
|
+
from alembic import op
|
|
12
|
+
|
|
13
|
+
revision: str = "7f2a9c1d4e8b"
|
|
14
|
+
down_revision: str | Sequence[str] | None = "5b9ac98bb150"
|
|
15
|
+
branch_labels: str | Sequence[str] | None = None
|
|
16
|
+
depends_on: str | Sequence[str] | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade() -> None:
|
|
20
|
+
op.execute("""
|
|
21
|
+
create index if not exists idx_daily_fact_day
|
|
22
|
+
on daily_fact (day);
|
|
23
|
+
""")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def downgrade() -> None:
|
|
27
|
+
op.execute("drop index if exists idx_daily_fact_day;")
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""add event_outbox table for transactional outbox
|
|
2
|
+
|
|
3
|
+
Revision ID: 3a8e61ac8ae2
|
|
4
|
+
Revises: v1_mod_init
|
|
5
|
+
Create Date: 2026-06-27 12:00:00.000000
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
|
|
11
|
+
from alembic import op
|
|
12
|
+
|
|
13
|
+
revision: str = "3a8e61ac8ae2"
|
|
14
|
+
down_revision: str | Sequence[str] | None = "v1_mod_init"
|
|
15
|
+
branch_labels: str | Sequence[str] | None = None
|
|
16
|
+
depends_on: str | Sequence[str] | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade() -> None:
|
|
20
|
+
op.execute("""
|
|
21
|
+
create table event_outbox (
|
|
22
|
+
id bigint generated always as identity primary key,
|
|
23
|
+
created_at timestamptz not null default now(),
|
|
24
|
+
event_name text not null,
|
|
25
|
+
payload jsonb not null,
|
|
26
|
+
payload_version smallint not null default 1,
|
|
27
|
+
idempotency_key text not null,
|
|
28
|
+
aggregate_type text not null,
|
|
29
|
+
aggregate_id text not null,
|
|
30
|
+
status text not null default 'pending',
|
|
31
|
+
available_at timestamptz not null default now(),
|
|
32
|
+
attempt_count integer not null default 0,
|
|
33
|
+
failure_count integer not null default 0,
|
|
34
|
+
locked_at timestamptz,
|
|
35
|
+
locked_by text,
|
|
36
|
+
last_error text,
|
|
37
|
+
metadata jsonb,
|
|
38
|
+
constraint event_outbox_idempotency_key_unique unique (idempotency_key),
|
|
39
|
+
constraint event_outbox_status_valid
|
|
40
|
+
check (status in ('pending', 'processing', 'done', 'dead'))
|
|
41
|
+
);
|
|
42
|
+
""")
|
|
43
|
+
|
|
44
|
+
op.execute("""
|
|
45
|
+
create index idx_event_outbox_pending_available
|
|
46
|
+
on event_outbox (available_at, id)
|
|
47
|
+
where status = 'pending';
|
|
48
|
+
""")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def downgrade() -> None:
|
|
52
|
+
op.execute("drop table if exists event_outbox;")
|
core_framework-2.3.0/alembic/moderation/alembic/versions/moderation_add_event_outbox_dead_indexes.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""add dead-row partial indexes on event_outbox for admin ops
|
|
2
|
+
|
|
3
|
+
Revision ID: de7c41fba3e8
|
|
4
|
+
Revises: 3a8e61ac8ae2
|
|
5
|
+
Create Date: 2026-06-28 12:00:00.000000
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
|
|
11
|
+
from alembic import op
|
|
12
|
+
|
|
13
|
+
revision: str = "de7c41fba3e8"
|
|
14
|
+
down_revision: str | Sequence[str] | None = "3a8e61ac8ae2"
|
|
15
|
+
branch_labels: str | Sequence[str] | None = None
|
|
16
|
+
depends_on: str | Sequence[str] | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade() -> None:
|
|
20
|
+
op.execute("""
|
|
21
|
+
create index idx_event_outbox_dead_created_at
|
|
22
|
+
on event_outbox (created_at desc, id)
|
|
23
|
+
where status = 'dead';
|
|
24
|
+
""")
|
|
25
|
+
op.execute("""
|
|
26
|
+
create index idx_event_outbox_dead_event_created
|
|
27
|
+
on event_outbox (event_name, created_at)
|
|
28
|
+
where status = 'dead';
|
|
29
|
+
""")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def downgrade() -> None:
|
|
33
|
+
op.execute("drop index if exists idx_event_outbox_dead_event_created;")
|
|
34
|
+
op.execute("drop index if exists idx_event_outbox_dead_created_at;")
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""add pending created_at partial index on event_outbox for admin oldest-pending
|
|
2
|
+
|
|
3
|
+
Revision ID: cf840f06eff3
|
|
4
|
+
Revises: de7c41fba3e8
|
|
5
|
+
Create Date: 2026-06-29 12:00:00.000000
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
|
|
11
|
+
from alembic import op
|
|
12
|
+
|
|
13
|
+
revision: str = "cf840f06eff3"
|
|
14
|
+
down_revision: str | Sequence[str] | None = "de7c41fba3e8"
|
|
15
|
+
branch_labels: str | Sequence[str] | None = None
|
|
16
|
+
depends_on: str | Sequence[str] | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade() -> None:
|
|
20
|
+
op.execute("""
|
|
21
|
+
create index idx_event_outbox_pending_created_at
|
|
22
|
+
on event_outbox (created_at, id)
|
|
23
|
+
where status = 'pending';
|
|
24
|
+
""")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def downgrade() -> None:
|
|
28
|
+
op.execute("drop index if exists idx_event_outbox_pending_created_at;")
|