core-framework 1.0.0__tar.gz → 1.2.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-1.2.0/.cursor/rules/api-layer.mdc +37 -0
- core_framework-1.2.0/.cursor/rules/constants-final.mdc +15 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/rules/domain-input-guards.mdc +3 -0
- core_framework-1.2.0/.cursor/rules/domain-repository-exceptions.mdc +20 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/rules/layer-boundaries.mdc +3 -2
- core_framework-1.2.0/.cursor/rules/no-code-in-docs.mdc +30 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/rules/repository-read-consistency.mdc +7 -5
- core_framework-1.2.0/.cursor/rules/strong-read-opt-in.mdc +22 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/rules/tech-stack.mdc +2 -0
- core_framework-1.2.0/.cursor/skills/add-domain/SKILL.md +203 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.github/workflows/_deploy.yml +3 -37
- {core_framework-1.0.0 → core_framework-1.2.0}/.github/workflows/dev-ci-cd.yaml +3 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/.github/workflows/test.yaml +3 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/.gitignore +4 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.pre-commit-config.yaml +1 -1
- core_framework-1.2.0/CHANGELOG.md +127 -0
- core_framework-1.2.0/LICENSE +21 -0
- core_framework-1.2.0/PKG-INFO +131 -0
- core_framework-1.2.0/README.md +97 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/comment/alembic/env.py +6 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/comment/alembic/versions/v1_comment_init_baseline.py +7 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/extension/alembic/env.py +6 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/moderation/alembic/env.py +6 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/moderation/alembic/versions/v1_mod_init_baseline.py +34 -37
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/notification/alembic/env.py +6 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/notification/alembic/versions/v1_notif_init_baseline.py +11 -4
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/post/alembic/env.py +6 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/post/alembic/versions/v1_post_init_baseline.py +26 -11
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/user/alembic/env.py +6 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/user/alembic/versions/v1_user_init_baseline.py +39 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/config.toml +2 -2
- {core_framework-1.0.0 → core_framework-1.2.0}/config.toml.template +1 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/admin/comments/router.py +17 -10
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/admin/moderation/router.py +31 -22
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/admin/users/router.py +12 -12
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/comments/authenticated/router.py +26 -13
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/comments/authenticated/schemas.py +1 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/comments/public/router.py +1 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/dependencies.py +1 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/events/router.py +5 -3
- core_framework-1.2.0/core_framework/api/notifications/authenticated/schemas.py +104 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/posts/authenticated/router.py +22 -8
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/posts/public/router.py +5 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/system/router.py +11 -8
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/users/authenticated/router.py +205 -9
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/users/authenticated/schemas.py +7 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/auth/access_service.py +7 -6
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/auth/auth_service.py +8 -3
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/bootstrap.py +3 -3
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/comments/admin_service.py +19 -9
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/comments/authenticated_service.py +25 -12
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/comments/public_service.py +14 -0
- core_framework-1.2.0/core_framework/application/events/event_service.py +72 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/events/event_token.py +6 -4
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/moderation/appeal_service.py +9 -3
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/moderation/user_service.py +14 -12
- core_framework-1.2.0/core_framework/application/notifications/README.md +16 -0
- core_framework-1.2.0/core_framework/application/notifications/enums.py +9 -0
- core_framework-1.2.0/core_framework/application/notifications/inbox_service.py +247 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/notifications/notification_service.py +27 -4
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/posts/aggregation_service.py +9 -2
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/posts/authenticated_service.py +6 -6
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/posts/public_service.py +29 -2
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/shared/enums.py +0 -5
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/shared/user_agent.py +1 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/users/admin_service.py +19 -13
- core_framework-1.2.0/core_framework/application/users/aggregation_service.py +18 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/users/authenticated_service.py +83 -6
- core_framework-1.2.0/core_framework/application/users/public_service.py +8 -0
- core_framework-1.2.0/core_framework/asgi.py +4 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/bundled_alembic.py +4 -18
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/core/__init__.py +3 -7
- core_framework-1.2.0/core_framework/core/cache.py +136 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/core/database.py +1 -4
- core_framework-1.2.0/core_framework/core/firebase.py +21 -0
- core_framework-1.2.0/core_framework/core/logging.py +83 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/core/observability.py +2 -7
- core_framework-1.2.0/core_framework/core/redis.py +57 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/core/runtime.py +2 -8
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/core/settings.py +3 -2
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/comment/README.md +3 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/comment/__init__.py +8 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/comment/exceptions.py +1 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/comment/models.py +17 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/comment/repository.py +46 -25
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/comment/service.py +20 -18
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/moderation/__init__.py +2 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/moderation/repository.py +66 -58
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/moderation/service.py +8 -8
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/notification/README.md +16 -15
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/notification/enums.py +2 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/notification/models.py +1 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/notification/repository.py +5 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/post/README.md +15 -7
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/post/enums.py +1 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/post/exceptions.py +1 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/post/models.py +8 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/post/repository.py +195 -42
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/post/service.py +59 -16
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/user/README.md +14 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/user/__init__.py +4 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/user/models.py +22 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/user/repository.py +209 -2
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/user/service.py +87 -9
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/utils.py +10 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/main.py +8 -14
- core_framework-1.2.0/core_framework/testing/__init__.py +7 -0
- core_framework-1.2.0/core_framework/testing/arq.py +80 -0
- {core_framework-1.0.0/tests/fixtures → core_framework-1.2.0/core_framework/testing}/auth.py +37 -43
- core_framework-1.2.0/core_framework/testing/config.py +28 -0
- core_framework-1.2.0/core_framework/testing/containers.py +63 -0
- core_framework-1.2.0/core_framework/testing/firebase.py +77 -0
- core_framework-1.2.0/core_framework/testing/hookspecs.py +14 -0
- core_framework-1.2.0/core_framework/testing/httpx_test_client.py +17 -0
- core_framework-1.2.0/core_framework/testing/migrations.py +38 -0
- core_framework-1.2.0/core_framework/testing/plugin.py +176 -0
- core_framework-1.2.0/core_framework/worker/main.py +73 -0
- core_framework-1.2.0/core_framework/worker/schedules/__init__.py +49 -0
- core_framework-1.2.0/core_framework/worker/schedules/schedule_aggregate_comment_stats.py +39 -0
- core_framework-1.2.0/core_framework/worker/schedules/schedule_aggregate_post_stats.py +39 -0
- core_framework-1.2.0/core_framework/worker/schedules/schedule_aggregate_user_stats.py +39 -0
- core_framework-1.2.0/core_framework/worker/schedules/schedule_expired_account_deletions.py +29 -0
- core_framework-1.2.0/core_framework/worker/schedules/schedule_expired_mute_lifts.py +29 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/worker/tasks/__init__.py +2 -0
- core_framework-1.2.0/core_framework/worker/tasks/process_account_deletion.py +15 -0
- core_framework-1.2.0/core_framework/worker/tasks/process_aggregate_comment_stats.py +21 -0
- core_framework-1.2.0/core_framework/worker/tasks/process_aggregate_post_stats.py +18 -0
- core_framework-1.2.0/core_framework/worker/tasks/process_aggregate_user_stats.py +18 -0
- core_framework-1.2.0/core_framework/worker/tasks/process_mute_lift.py +15 -0
- core_framework-1.2.0/core_framework/worker/worker_context.py +20 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docker-compose.yml +1 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/dockerfile +17 -16
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/api.md +14 -4
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/architecture-decisions.md +79 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/architecture.md +21 -7
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/conventions.md +11 -10
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/core-framework-migration.md +21 -12
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/database-triggers.md +17 -45
- core_framework-1.2.0/docs/event-outbox-design.md +528 -0
- core_framework-1.2.0/docs/flows/comments/comment_stats_aggregation.md +69 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/comments/retrieve_comments.md +2 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/events/events.md +4 -3
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/mentions/mentions_in_content.md +1 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/notifications/notification_inbox.md +46 -37
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/posts/author_context.md +1 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/posts/hashtag_discovery.md +3 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/posts/post_like.md +1 -1
- core_framework-1.2.0/docs/flows/posts/post_stats_aggregation.md +81 -0
- core_framework-1.2.0/docs/flows/users/follow.md +215 -0
- core_framework-1.2.0/docs/flows/users/my_posts_and_comments.md +108 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/users/user_removal.md +2 -2
- core_framework-1.2.0/docs/follow-system-design.md +419 -0
- core_framework-1.2.0/docs/package-api.md +287 -0
- core_framework-1.2.0/docs/testing-plugin-design.md +391 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/makefile +2 -7
- {core_framework-1.0.0 → core_framework-1.2.0}/pyproject.toml +28 -8
- core_framework-1.2.0/tests/conftest.py +25 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/api/_http_helpers.py +1 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/api/admin/posts/router_test.py +1 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/api/admin/users/router_test.py +30 -171
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/api/auth/router_test.py +1 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/api/comments/authenticated/comment_writes_integration_test.py +1 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/api/comments/public/router_test.py +75 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/api/events/router_test.py +12 -21
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/api/notifications/router_test.py +79 -22
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/api/posts/authenticated/post_writes_integration_test.py +1 -1
- core_framework-1.2.0/tests/integration/api/posts/followers_visibility_test.py +123 -0
- core_framework-1.2.0/tests/integration/api/posts/post_stats_dirty_marking_test.py +219 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/api/posts/public/router_test.py +64 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/api/system/router_test.py +8 -4
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/api/users/authenticated/router_test.py +79 -3
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/api/users/public/router_test.py +1 -1
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/worker/account_deletion_test.py +66 -91
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/worker/aggregate_comment_stats_test.py +78 -70
- core_framework-1.2.0/tests/integration/worker/aggregate_post_stats_test.py +267 -0
- core_framework-1.2.0/tests/integration/worker/aggregate_user_stats_test.py +144 -0
- core_framework-1.2.0/tests/integration/worker/conftest.py +21 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/worker/mute_lift_test.py +47 -32
- core_framework-1.2.0/tests/integration/worker/utils_test.py +33 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/unit/application/events/event_service_test.py +3 -3
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/unit/application/notifications/inbox_service_test.py +8 -8
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/unit/domains/user/service_test.py +5 -5
- {core_framework-1.0.0 → core_framework-1.2.0}/uv.lock +299 -254
- core_framework-1.0.0/.cursor/rules/api-layer.mdc +0 -27
- core_framework-1.0.0/CHANGELOG.md +0 -119
- core_framework-1.0.0/PKG-INFO +0 -22
- core_framework-1.0.0/README.md +0 -36
- core_framework-1.0.0/core_framework/api/notifications/authenticated/schemas.py +0 -51
- core_framework-1.0.0/core_framework/application/events/event_service.py +0 -51
- core_framework-1.0.0/core_framework/application/notifications/inbox_service.py +0 -107
- core_framework-1.0.0/core_framework/application/users/public_service.py +0 -7
- core_framework-1.0.0/core_framework/core/cache.py +0 -234
- core_framework-1.0.0/core_framework/core/logging.py +0 -99
- core_framework-1.0.0/core_framework/core/redis.py +0 -135
- core_framework-1.0.0/core_framework/worker/main.py +0 -56
- core_framework-1.0.0/core_framework/worker/schedules/__init__.py +0 -35
- core_framework-1.0.0/core_framework/worker/schedules/schedule_aggregate_comment_stats.py +0 -32
- core_framework-1.0.0/core_framework/worker/schedules/schedule_aggregate_post_view_counts.py +0 -28
- core_framework-1.0.0/core_framework/worker/schedules/schedule_expired_account_deletions.py +0 -24
- core_framework-1.0.0/core_framework/worker/schedules/schedule_expired_mute_lifts.py +0 -24
- core_framework-1.0.0/core_framework/worker/tasks/process_account_deletion.py +0 -13
- core_framework-1.0.0/core_framework/worker/tasks/process_aggregate_comment_stats.py +0 -19
- core_framework-1.0.0/core_framework/worker/tasks/process_aggregate_post_stats.py +0 -12
- core_framework-1.0.0/core_framework/worker/tasks/process_mute_lift.py +0 -13
- core_framework-1.0.0/dockerfile.worker +0 -43
- core_framework-1.0.0/docs/flows/comments/comment_stats_aggregation.md +0 -134
- core_framework-1.0.0/docs/flows/posts/post_stats_aggregation.md +0 -63
- core_framework-1.0.0/docs/package-api.md +0 -149
- core_framework-1.0.0/tests/conftest.py +0 -269
- core_framework-1.0.0/tests/integration/worker/aggregate_post_view_test.py +0 -205
- core_framework-1.0.0/tests/integration/worker/conftest.py +0 -25
- core_framework-1.0.0/tests/integration/worker/utils_test.py +0 -54
- core_framework-1.0.0/tests/unit/core/cache_test.py +0 -692
- core_framework-1.0.0/tests/unit/domains/comment/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/rules/api-reference-docs.mdc +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/rules/api-security.mdc +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/rules/api-validation.mdc +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/rules/application-layer.mdc +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/rules/database-triggers.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/rules/domain-caller-context.mdc +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/rules/domain-imports.mdc +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/rules/flow-documentation.mdc +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/rules/implementation-workflow.mdc +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/rules/integration-test-strategy.mdc +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/rules/no-docstrings.mdc +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/rules/postgres-config-conventions.mdc +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/skills/add-config/SKILL.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/skills/code-review/SKILL.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.cursor/skills/recommend-features/SKILL.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.dockerignore +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.github/workflows/manual-deployment.yaml +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.logfire/.gitignore +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.logfire/logfire_credentials.json +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/.python-version +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/comment/alembic/README +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/comment/alembic/script.py.mako +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/comment/alembic.ini +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/extension/alembic/README +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/extension/alembic/script.py.mako +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/extension/alembic/versions/v1_ext_init_baseline.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/extension/alembic.ini +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/moderation/alembic/README +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/moderation/alembic/script.py.mako +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/moderation/alembic.ini +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/notification/alembic/README +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/notification/alembic/script.py.mako +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/notification/alembic.ini +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/post/alembic/README +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/post/alembic/script.py.mako +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/post/alembic.ini +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/user/alembic/README +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/user/alembic/script.py.mako +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/alembic/user/alembic.ini +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/admin/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/admin/comments/schemas.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/admin/moderation/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/admin/moderation/schemas.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/admin/posts/router.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/admin/posts/schemas.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/admin/router.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/admin/users/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/admin/users/schemas.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/auth/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/auth/router.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/auth/schemas.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/comments/public/schemas.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/comments/router.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/comments/schemas.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/constants.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/events/schemas.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/notifications/authenticated/router.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/notifications/router.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/posts/authenticated/schemas.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/posts/public/schemas.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/posts/router.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/posts/schemas.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/router.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/schemas.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/system/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/users/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/users/authenticated/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/users/public/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/users/public/router.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/users/public/schemas.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/users/router.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/api/users/shared/schemas.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/auth/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/auth/models.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/comments/aggregation_service.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/events/README.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/events/models.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/moderation/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/moderation/moderator_service.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/moderation/report_service.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/moderation/scheduled_service.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/notifications/mute_service.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/posts/admin_service.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/shared/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/shared/exceptions.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/users/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/application/users/scheduled_service.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/constants.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/core/context.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/core/exception_handlers/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/core/exception_handlers/comment.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/core/exception_handlers/common.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/core/exception_handlers/moderation.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/core/exception_handlers/notification.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/core/exception_handlers/post.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/core/exception_handlers/setup.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/core/exception_handlers/user.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/core/http_client.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/core/middleware.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/core/pagination.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/comment/constants.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/comment/dependencies.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/comment/enums.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/exceptions.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/moderation/README.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/moderation/dependencies.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/moderation/enums.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/moderation/exceptions.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/moderation/models.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/notification/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/notification/dependencies.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/notification/exceptions.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/notification/service.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/post/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/post/constants.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/post/dependencies.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/user/constants.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/user/dependencies.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/user/enums.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/user/exceptions.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/domains/user/utils.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/migrate_cli.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/core_framework/worker/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/auth/access_control.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/auth/registration.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/comments/admin_comments.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/comments/comment_report.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/comments/create_comment.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/comments/delete_comment.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/comments/edit_comment.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/moderation/appeals.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/moderation/internal_notes.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/moderation/moderator_actions.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/moderation/reports.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/moderation/restrictions.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/posts/admin_posts.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/users/account.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/users/account_deletion.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/users/blocks.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/users/change_history.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/users/check_username_exists.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/users/preferences.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/docs/flows/users/profile.md +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/__init__.py +0 -0
- {core_framework-1.0.0/tests/fixtures → core_framework-1.2.0/tests/integration}/__init__.py +0 -0
- {core_framework-1.0.0/tests/integration → core_framework-1.2.0/tests/integration/api}/__init__.py +0 -0
- {core_framework-1.0.0/tests/integration/api → core_framework-1.2.0/tests/integration/api/admin}/__init__.py +0 -0
- {core_framework-1.0.0/tests/integration/api/admin → core_framework-1.2.0/tests/integration/api/admin/comments}/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/api/admin/comments/router_test.py +0 -0
- {core_framework-1.0.0/tests/integration/api/admin/comments → core_framework-1.2.0/tests/integration/api/admin/moderation}/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/api/admin/moderation/router_test.py +0 -0
- {core_framework-1.0.0/tests/integration/api/admin/moderation → core_framework-1.2.0/tests/integration/api/admin/posts}/__init__.py +0 -0
- {core_framework-1.0.0/tests/integration/api/admin/posts → core_framework-1.2.0/tests/integration/api/admin/users}/__init__.py +0 -0
- {core_framework-1.0.0/tests/integration/api/admin/users → core_framework-1.2.0/tests/integration/api/auth}/__init__.py +0 -0
- {core_framework-1.0.0/tests/integration/api/auth → core_framework-1.2.0/tests/integration/api/comments}/__init__.py +0 -0
- {core_framework-1.0.0/tests/integration/api/comments → core_framework-1.2.0/tests/integration/api/comments/public}/__init__.py +0 -0
- {core_framework-1.0.0/tests/integration/api/comments/public → core_framework-1.2.0/tests/integration/api/notifications}/__init__.py +0 -0
- {core_framework-1.0.0/tests/integration/api/notifications → core_framework-1.2.0/tests/integration/api/posts}/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/integration/api/posts/comment_count_aggregation_test.py +0 -0
- {core_framework-1.0.0/tests/integration/api/posts → core_framework-1.2.0/tests/integration/api/posts/public}/__init__.py +0 -0
- {core_framework-1.0.0/tests/integration/api/posts/public → core_framework-1.2.0/tests/integration/api/system}/__init__.py +0 -0
- {core_framework-1.0.0/tests/integration/api/system → core_framework-1.2.0/tests/integration/api/users}/__init__.py +0 -0
- {core_framework-1.0.0/tests/integration/api/users → core_framework-1.2.0/tests/integration/api/users/authenticated}/__init__.py +0 -0
- {core_framework-1.0.0/tests/integration/api/users/authenticated → core_framework-1.2.0/tests/integration/api/users/public}/__init__.py +0 -0
- {core_framework-1.0.0/tests/integration/api/users/public → core_framework-1.2.0/tests/integration/worker}/__init__.py +0 -0
- {core_framework-1.0.0/tests/integration/worker → core_framework-1.2.0/tests/unit}/__init__.py +0 -0
- {core_framework-1.0.0/tests/unit → core_framework-1.2.0/tests/unit/application/comments}/__init__.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/unit/core/bundled_alembic_test.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/unit/core/migrate_cli_test.py +0 -0
- {core_framework-1.0.0 → core_framework-1.2.0}/tests/unit/core/pagination_test.py +0 -0
- {core_framework-1.0.0/tests/unit/application/comments → core_framework-1.2.0/tests/unit/domains}/__init__.py +0 -0
- {core_framework-1.0.0/tests/unit/domains → core_framework-1.2.0/tests/unit/domains/comment}/__init__.py +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: API layer should contain HTTP adapters only
|
|
3
|
+
globs:
|
|
4
|
+
- core_framework/api/**/*.py
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# API Layer Conventions
|
|
8
|
+
|
|
9
|
+
The API layer is an HTTP adapter, not a business-logic layer.
|
|
10
|
+
|
|
11
|
+
## Scope
|
|
12
|
+
|
|
13
|
+
- Keep API files focused on routers, schemas, and HTTP dependencies.
|
|
14
|
+
- API code may call application services in `core_framework/application/...`.
|
|
15
|
+
- API code must not contain orchestration shared with workers.
|
|
16
|
+
|
|
17
|
+
## Router Responsibilities
|
|
18
|
+
|
|
19
|
+
- Routers own HTTP concerns: status codes, `HTTPException`, `response_model`, request parsing.
|
|
20
|
+
- When a route sets **`response_model`**, annotate the handler return type as **`typing.Any`** (OpenAPI documents the real shape; the handler may return dicts or model-adjacent values before serialization). Routes with no response body (for example **`204`** and no **`response_model`**) continue to use **`None`**.
|
|
21
|
+
- Routers call `validate_*` schema helpers before invoking application services.
|
|
22
|
+
- Routers should remain thin and delegate business decisions to application/domain layers.
|
|
23
|
+
|
|
24
|
+
## Route decorators (OpenAPI)
|
|
25
|
+
|
|
26
|
+
- Do **not** set **`summary=`** or **`description=`** on **`@router.get`**, **`post`**, **`patch`**, **`put`**, **`delete`**, or **`APIRoute`-style** registrations. Rely on paths, handler names, **`response_model`**, and Pydantic schemas. Do **not** add route **docstrings** for the same purpose either (see **`no-docstrings`**).
|
|
27
|
+
|
|
28
|
+
## Response Models
|
|
29
|
+
|
|
30
|
+
- **Do not** use bare collection types (e.g. `list[Item]`) as `response_model`. Wrap them in a named response `BaseModel` with an **`items`** field (e.g. `class FooListResponse(BaseModel): items: list[FooItem]`).
|
|
31
|
+
- This keeps responses extensible (pagination, counts, metadata) without breaking the contract.
|
|
32
|
+
|
|
33
|
+
## What to Avoid
|
|
34
|
+
|
|
35
|
+
- Do NOT implement reusable business workflows in API modules.
|
|
36
|
+
- Do NOT import domain internals directly; follow domain public API and dependencies rules.
|
|
37
|
+
- Do NOT place worker-only logic (scheduling/task orchestration) in API modules.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Prefer typing.Final for module-level constants (not reassigned)
|
|
3
|
+
globs: core_framework/**/*.py
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Module-level constants and `Final`
|
|
8
|
+
|
|
9
|
+
When introducing or editing **module-level constants** (values that are fixed for the lifetime of the module and not meant to be reassigned):
|
|
10
|
+
|
|
11
|
+
- Annotate them with **`typing.Final`** and an explicit type, e.g. **`NAME: Final[int] = 42`** or **`_INTERNAL: Final[str] = "henry"`**.
|
|
12
|
+
- Prefer **`FrozenSet`**, **`frozenset` literals**, **`MappingProxyType`**, or immutable collections for grouped constants when mutation must be prevented — e.g. **`USER_PREFERENCE_UPDATE_COLUMNS`** in **`core_framework/domains/user/constants.py`**.
|
|
13
|
+
- **`Final`** is for **assignment** semantics (no rebinding): it does not freeze mutable object contents unless the type/container is immutable.
|
|
14
|
+
|
|
15
|
+
Do not refactor unrelated legacy constants only to add **`Final`** unless you are already touching that symbol.
|
|
@@ -31,6 +31,8 @@ Use **layered** checks: user contracts at the **API**, business preconditions at
|
|
|
31
31
|
|
|
32
32
|
### Shared string / pagination helpers (`core_framework.domains.utils`)
|
|
33
33
|
|
|
34
|
+
**Import boundary:** `domains/utils.py` may import **`MAX_PAGE_FETCH_SIZE`** from **`core_framework.core.pagination`** only. That module is shared pagination constants, not Postgres/Redis/HTTP infrastructure; it is not subject to the “core imports only in `dependencies.py`” rule for DB/cache clients.
|
|
35
|
+
|
|
34
36
|
Use these across domains so validation stays consistent and call sites stay readable:
|
|
35
37
|
|
|
36
38
|
| Helper | Use when |
|
|
@@ -39,6 +41,7 @@ Use these across domains so validation stays consistent and call sites stay read
|
|
|
39
41
|
| **`require_non_blank_kwargs(a_id=..., b_id=...)`** | Several required strings at once; each **keyword name** is the field label in `DomainValidationError` (e.g. `author_id=author_id, content=content`). |
|
|
40
42
|
| **`require_non_blank_if_not_none(viewer_id=..., ...)`** | Optional `str \| None` arguments: validate only when not `None` (still rejects `""`). |
|
|
41
43
|
| **`validate_pagination_limit(limit=...)`** | Paginated reads: `limit` in `[1, MAX_PAGE_FETCH_SIZE]` (aligned with `core_framework.core.pagination`). |
|
|
44
|
+
| **`validate_pagination_offset(offset=...)`** | Paginated reads: `offset` must be **≥ 0** (aligned with `OffsetQueryParams`). |
|
|
42
45
|
| **`require_positive_int(value=..., field=...)`** | **Integer** surrogate keys only (e.g. moderation `report_id`, `appeal_id`, `note_id`). Must be **≥ 1**. Domains that identify entities with **string** IDs (ULIDs) use **`require_non_blank_*`** instead—do not add this helper there. |
|
|
43
46
|
| **`require_validated_update_has_allowed_fields(validated_update_request=..., allowed_fields=...)`** | PATCH-style dict updates: reject empty bodies and bodies with **no** recognized keys (use domain constants such as **`USER_*_UPDATE_FIELDS`**). |
|
|
44
47
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: When domain repositories vs services raise domain exceptions (thin façade)
|
|
3
|
+
globs:
|
|
4
|
+
- core_framework/domains/**/*.py
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Domain: repository exceptions vs service
|
|
8
|
+
|
|
9
|
+
## Thin service + single persistence step
|
|
10
|
+
|
|
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, **prefer raising domain exceptions inside the repository** when the persistence outcome is an error condition (empty `RETURNING`, unexpected row absence on insert).
|
|
12
|
+
|
|
13
|
+
The service stays a façade: validate arguments, then **`await`** the repository method and let **domain exceptions** propagate.
|
|
14
|
+
|
|
15
|
+
## Prefer service-side translation when
|
|
16
|
+
|
|
17
|
+
- The service **composes multiple** repository calls, **branches** on outcomes, or maps the same SQL outcome to **different domain errors**.
|
|
18
|
+
- Keeping the repository purely “data shaped” (**`Optional`** / **`bool`**) materially improves reuse or clarity for **multiple** service entrypoints calling the same repository method.
|
|
19
|
+
|
|
20
|
+
Existing domains may mix both styles; use this guide for **new work** and when **refactoring** a call site for clarity.
|
|
@@ -19,7 +19,8 @@ Use this architecture split consistently:
|
|
|
19
19
|
## Rules
|
|
20
20
|
|
|
21
21
|
- API layer must not contain business orchestration that is shared with workers.
|
|
22
|
-
- Worker layer must not contain domain business rules; call application services.
|
|
22
|
+
- Worker layer must not contain domain business rules; call application services only.
|
|
23
|
+
- Worker code must not import **`core_framework.domains`** packages (no direct domain repository/service usage from workers).
|
|
23
24
|
- Application layer must not import FastAPI/Starlette HTTP types.
|
|
24
25
|
- Domain layer must not import API, worker, or application modules.
|
|
25
26
|
- Domain layer must not import global infrastructure except in `dependencies.py`.
|
|
@@ -29,7 +30,7 @@ Use this architecture split consistently:
|
|
|
29
30
|
Dependencies point inward:
|
|
30
31
|
|
|
31
32
|
- API -> application and/or domains
|
|
32
|
-
- worker -> application
|
|
33
|
+
- worker -> application
|
|
33
34
|
- application -> domains
|
|
34
35
|
|
|
35
36
|
Never the reverse.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Docs avoid implementation code; HTTP interface listings and schema DDL are OK
|
|
3
|
+
globs: docs/**/*.md
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# No Code in Documentation
|
|
8
|
+
|
|
9
|
+
Documentation under **`docs/`** must not carry **implementation** snippets that belong in the repo (executable Python, shell, operational SQL queries, etc.). **`docs/`** describes **what** and **why**; the codebase shows **how**.
|
|
10
|
+
|
|
11
|
+
## What to exclude
|
|
12
|
+
|
|
13
|
+
- Python class/function definitions or examples
|
|
14
|
+
- **SQL for data access or mutation** — `SELECT`, `INSERT`, `UPDATE`, `DELETE`, `MERGE`, CTE-heavy reports, cleanup queries, etc. (describe behavior in prose or name the operation; do not paste DML/query text)
|
|
15
|
+
- Shell commands
|
|
16
|
+
- Pseudocode that mimics real code syntax
|
|
17
|
+
- Inline backticked references to internal function/method/class names when prose suffices (prefer describing behavior)
|
|
18
|
+
|
|
19
|
+
## What is allowed
|
|
20
|
+
|
|
21
|
+
- **HTTP API interface listings** — fenced **`http`** blocks or plain lines listing `METHOD /path # short note`. That is **surface contract**, not implementation code.
|
|
22
|
+
- **SQL for table creation / physical design** — e.g. `CREATE TABLE` (and table-level DDL in the same design story, such as `CREATE INDEX`). DDL is a design artifact, not runtime query text.
|
|
23
|
+
- ASCII diagrams and flowcharts (box-drawing characters)
|
|
24
|
+
- Markdown tables of fields and types (`jsonb`, `varchar(26)`, etc.)
|
|
25
|
+
- Plain-text lifecycle diagrams (arrows, boxes)
|
|
26
|
+
- File/directory tree layouts (plain text or a neutral fence such as `text` if helpful)
|
|
27
|
+
|
|
28
|
+
## Rationale
|
|
29
|
+
|
|
30
|
+
Implementation snippets in docs drift from source. **HTTP listings** stay stable as the public contract. **DDL** documents intended physical shape. **DML** and **application code** belong in migrations and modules.
|
|
@@ -5,10 +5,12 @@ alwaysApply: true
|
|
|
5
5
|
|
|
6
6
|
# Repository Read Consistency
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
**Policy** (see **`strong-read-opt-in`**): use **strong** reads for **read-after-write** refetches in the same request or job; treat **other** strong-read additions as **opt-in** unless the user asked for them in that task. Repositories may still take **`strong_read_database`** on **`__init__`** for wiring symmetry while it remains unused.
|
|
9
|
+
|
|
10
|
+
When a repository supports both replica and strong reads, choose consistency at the use-case level.
|
|
9
11
|
|
|
10
12
|
- Keep normal reads explicit (for example, `select_appeal`).
|
|
11
|
-
- Add separate strong-read methods for
|
|
13
|
+
- Add separate strong-read methods only where needed for those flows (for example, `select_appeal_strong`).
|
|
12
14
|
- Services decide which method to call based on consistency needs.
|
|
13
15
|
|
|
14
16
|
## Method Structure
|
|
@@ -29,9 +31,9 @@ When a repository supports both replica reads and strong reads, choose consisten
|
|
|
29
31
|
|
|
30
32
|
## Consistency Selection
|
|
31
33
|
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
34
|
+
- **Read-after-write** in the same request or worker job → **strong** read (required).
|
|
35
|
+
- **Decision-critical guards** where stale data causes wrong branching → strong reads only when the task adds that surface (**opt-in** unless the flow is read-after-write).
|
|
36
|
+
- **List/browse/history** → normal reads unless strict freshness is explicitly required (**opt-in**).
|
|
35
37
|
- Keep consistency decisions in services/use-case orchestration, not hidden in unrelated call paths.
|
|
36
38
|
|
|
37
39
|
```python
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Strong reads after writes are required; other strong-read surface is opt-in
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Strong reads (core-framework)
|
|
7
|
+
|
|
8
|
+
When editing **`core_framework/**/*.py`** in this repository:
|
|
9
|
+
|
|
10
|
+
## Read-after-write (required)
|
|
11
|
+
|
|
12
|
+
After a **write** in the **same** HTTP request or worker job flow, any **refetch** of that persisted state must use a **strong** read (`strong_read_database`, **`*_strong`** repository methods, or service accessors that call them) so callers do not hit stale replica data. 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.
|
|
13
|
+
|
|
14
|
+
## Other uses (opt-in)
|
|
15
|
+
|
|
16
|
+
Do **not** add **`strong_read_database`** usage, new **`*_strong`** repository or service accessors, or strong refetches for reasons **other** than read-after-write (for example list/browse freshness, or decision guards unrelated to an immediate prior write in the same flow) unless the **user explicitly requested** strong reads in the same task or message.
|
|
17
|
+
|
|
18
|
+
## Wiring
|
|
19
|
+
|
|
20
|
+
Repositories should still accept **`strong_read_database: Postgres`** on **`__init__`** where that matches existing dependency wiring **even when no method reads from it yet**; keep **`self.strong_read_database`** unused until a caller needs strong reads.
|
|
21
|
+
|
|
22
|
+
Existing `*_strong` APIs and call sites stay as-is unless a task explicitly changes them.
|
|
@@ -9,4 +9,6 @@ alwaysApply: true
|
|
|
9
9
|
- **Exception syntax**: In this codebase, Python 3.14 comma-style multi-exception handling (for example, `except A, B:`) is valid and intentional.
|
|
10
10
|
- **Pydantic v2**: Use only Pydantic v2 APIs. No v1 patterns (e.g. use `model_dump` not `dict()`, `model_validate` not `parse_obj`, `model_config` not nested `Config` class, `field_validator` not `validator`).
|
|
11
11
|
- **Prefer newer APIs**: When multiple APIs exist for the same purpose, prefer the newer, recommended one (e.g. `asyncio.TaskGroup` over `asyncio.gather` for concurrent async operations).
|
|
12
|
+
- **Imports**: Use **absolute** imports only (`from core_framework....`). Do not use relative imports (`from .module import ...`, `from ..parent import ...`). Applies across application, domains, API, core, workers, and tests (including **`__init__.py`** re-exports).
|
|
13
|
+
- **orjson over stdlib json**: Use `orjson` for all JSON serialization and deserialization. Do not use `import json` from the standard library.
|
|
12
14
|
- **PostgreSQL 18**: SQL and migrations must be compatible with PostgreSQL 18. Use supported syntax and avoid deprecated features.
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-domain
|
|
3
|
+
description: Scaffold a new domain in core-framework (settings, empty Alembic tree, domain package + bootstrap). Use when adding a brand-new domain under `core_framework/domains/<name>/` with a dedicated Postgres schema name and `alembic/<name>/` layout, when the user asks to "add a domain", "create a domain", or "bootstrap <name> domain".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Adding a New Domain (with its own DB schema)
|
|
7
|
+
|
|
8
|
+
This skill covers **scaffolding only**: settings, a full **Alembic directory layout** with an **empty** `versions/` folder (no `.py` migrations yet), domain package shells + bootstrap wiring.
|
|
9
|
+
|
|
10
|
+
**Do not** add `<domain>` to `ALEMBIC_DOMAINS` until at least one revision exists under `versions/` — otherwise `cf-alembic` would run `upgrade head` for an empty branch and fail.
|
|
11
|
+
|
|
12
|
+
It does **not** cover the API layer or tests — handle those separately following the existing rules.
|
|
13
|
+
|
|
14
|
+
Follow `implementation-workflow` (`/.cursor/rules/implementation-workflow.mdc`): one step per response, wait for review.
|
|
15
|
+
|
|
16
|
+
## Inputs
|
|
17
|
+
|
|
18
|
+
- `<domain>` — snake_case singular name (e.g. `billing`, `audit`, `feed`).
|
|
19
|
+
- Postgres schema name — usually `<domain>` (no quoting needed unless reserved like `user`).
|
|
20
|
+
|
|
21
|
+
Reserve the schema name in config so code and future migrations agree; the **physical schema in Postgres** does not exist until a migration creates it.
|
|
22
|
+
|
|
23
|
+
## Order
|
|
24
|
+
|
|
25
|
+
1. Schema config wiring
|
|
26
|
+
1. Alembic tree (all support files; **`versions/` empty**)
|
|
27
|
+
1. Domain skeleton (files, **class shells only**, dependencies + bootstrap wiring — **no example methods, no SQL/queries**)
|
|
28
|
+
1. Application bootstrap wiring
|
|
29
|
+
1. Domain README and changelog
|
|
30
|
+
|
|
31
|
+
Stop after each step and wait for review.
|
|
32
|
+
|
|
33
|
+
## 1. Schema config wiring
|
|
34
|
+
|
|
35
|
+
Per `postgres-config-conventions` (`/.cursor/rules/postgres-config-conventions.mdc`).
|
|
36
|
+
|
|
37
|
+
`core_framework/core/settings.py`:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
class PostgresSchemasConfig(BaseModel):
|
|
41
|
+
schema_extension: str
|
|
42
|
+
schema_user: str
|
|
43
|
+
schema_moderation: str
|
|
44
|
+
schema_post: str
|
|
45
|
+
schema_comment: str
|
|
46
|
+
schema_notification: str
|
|
47
|
+
schema_<domain>: str # add
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`config.toml` and `config.toml.template` — add under `[postgres_schemas]`:
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
[postgres_schemas]
|
|
54
|
+
schema_<domain> = "<domain>"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Do **not** add the schema name anywhere else. Schema lives only in `[postgres_schemas]`.
|
|
58
|
+
|
|
59
|
+
## 2. Alembic tree (skeleton, empty `versions/`)
|
|
60
|
+
|
|
61
|
+
Create `alembic/<domain>/` by copying layout from an existing domain (e.g. `alembic/comment/`): **`alembic.ini`**, `alembic/env.py`, `alembic/script.py.mako`, `alembic/README`, and directory **`alembic/versions/`**.
|
|
62
|
+
|
|
63
|
+
- **`env.py`**: keep the same pattern as siblings (e.g. `config.set_main_option("sqlalchemy.url", settings.write_postgres.alembic_postgres_url)`).
|
|
64
|
+
- **`alembic/versions/`**: **must contain no `*.py` files** at scaffold time. Optional: add a **`.gitkeep`** so Git keeps the empty folder.
|
|
65
|
+
|
|
66
|
+
**Do not** append `<domain>` to `ALEMBIC_DOMAINS` in this step. The new tree is **not** part of `cf-alembic` until the follow-up adds the first revision **and** updates `ALEMBIC_DOMAINS` together.
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
alembic/<domain>/
|
|
70
|
+
├── alembic.ini
|
|
71
|
+
└── alembic/
|
|
72
|
+
├── env.py
|
|
73
|
+
├── README
|
|
74
|
+
├── script.py.mako
|
|
75
|
+
└── versions/
|
|
76
|
+
└── .gitkeep # optional; no *.py here initially
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 3. Domain skeleton (shells + wiring only)
|
|
80
|
+
|
|
81
|
+
Create `core_framework/domains/<domain>/` with **stub types and wiring only**. Do **not** add example domain methods, repository queries, SQL strings, or business logic until a later change.
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
core_framework/domains/<domain>/
|
|
85
|
+
├── __init__.py
|
|
86
|
+
├── README.md
|
|
87
|
+
├── exceptions.py
|
|
88
|
+
├── models.py # placeholder only (see below)
|
|
89
|
+
├── repository.py
|
|
90
|
+
├── service.py
|
|
91
|
+
├── dependencies.py
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Skip `enums.py`, `constants.py`, and `utils.py` until needed.
|
|
95
|
+
|
|
96
|
+
### `exceptions.py`
|
|
97
|
+
|
|
98
|
+
One base exception class only, e.g. `class Base<Domain>Exception(Exception): ...` with an empty body (or `pass`). No concrete exception subclasses yet unless required for imports.
|
|
99
|
+
|
|
100
|
+
### `models.py`
|
|
101
|
+
|
|
102
|
+
No dataclasses or table-shaped models at scaffold time. Either an empty module or a single short comment that models will be added with the first real migration. **Do not** invent placeholder fields.
|
|
103
|
+
|
|
104
|
+
### `repository.py`
|
|
105
|
+
|
|
106
|
+
A single `class <Domain>Repository:` whose **`__init__`** takes `(write_postgres, read_postgres, strong_read_postgres)` (or the project’s `Postgres` type) and assigns them to **private** instance attributes (e.g. `_write_database`, `_read_database`, `_strong_read_database`). **No other methods.**
|
|
107
|
+
|
|
108
|
+
### `service.py`
|
|
109
|
+
|
|
110
|
+
A single `class <Domain>Service:` whose **`__init__`** takes `<Domain>Repository` and stores it on a **private** attribute (e.g. `_repository`). **No other methods.** No FastAPI/Starlette imports. No caller-context names per `domain-caller-context`.
|
|
111
|
+
|
|
112
|
+
When real behavior is added later, apply `repository-read-consistency` (`/.cursor/rules/repository-read-consistency.mdc`) for reads and `_strong` naming.
|
|
113
|
+
|
|
114
|
+
### `__init__.py`
|
|
115
|
+
|
|
116
|
+
Export the **classes** other layers may import: base exception, `<Domain>Repository`, `<Domain>Service`. List them in `__all__` per `domain-imports`. Do **not** re-export module-level `*_repository` / `*_service` instances from `dependencies` here.
|
|
117
|
+
|
|
118
|
+
### `dependencies.py`
|
|
119
|
+
|
|
120
|
+
Wire repository and service the same way as other domains (e.g. `domains/comment/dependencies.py`):
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
from core_framework.core.runtime import CoreRuntime, unconfigured_dependency
|
|
124
|
+
from core_framework.domains.<domain>.repository import <Domain>Repository
|
|
125
|
+
from core_framework.domains.<domain>.service import <Domain>Service
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def build_<domain>_repository(runtime: CoreRuntime) -> <Domain>Repository:
|
|
129
|
+
return <Domain>Repository(
|
|
130
|
+
runtime.write_postgres,
|
|
131
|
+
runtime.read_postgres,
|
|
132
|
+
runtime.write_postgres, # strong placeholder until a dedicated handle exists
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def build_<domain>_service(runtime: CoreRuntime) -> <Domain>Service:
|
|
137
|
+
return <Domain>Service(build_<domain>_repository(runtime))
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def configure_<domain>_dependencies(runtime: CoreRuntime) -> None:
|
|
141
|
+
global <domain>_repository, <domain>_service
|
|
142
|
+
<domain>_repository = build_<domain>_repository(runtime)
|
|
143
|
+
<domain>_service = build_<domain>_service(runtime)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
<domain>_repository = unconfigured_dependency("<Domain>Repository")
|
|
147
|
+
<domain>_service = unconfigured_dependency("<Domain>Service")
|
|
148
|
+
|
|
149
|
+
__all__ = [
|
|
150
|
+
"build_<domain>_repository",
|
|
151
|
+
"build_<domain>_service",
|
|
152
|
+
"configure_<domain>_dependencies",
|
|
153
|
+
"<domain>_repository",
|
|
154
|
+
"<domain>_service",
|
|
155
|
+
]
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## 4. Application bootstrap wiring
|
|
159
|
+
|
|
160
|
+
`core_framework/application/bootstrap.py` — add the configure call:
|
|
161
|
+
|
|
162
|
+
```text
|
|
163
|
+
from core_framework.domains.<domain>.dependencies import configure_<domain>_dependencies
|
|
164
|
+
|
|
165
|
+
def configure_application_dependencies(*, runtime: CoreRuntime) -> None:
|
|
166
|
+
...
|
|
167
|
+
configure_<domain>_dependencies(runtime)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Worker `startup` and API `init_app` already call `configure_application_dependencies(runtime=...)`, so both processes pick up the new domain automatically.
|
|
171
|
+
|
|
172
|
+
## 5. Docs
|
|
173
|
+
|
|
174
|
+
- `core_framework/domains/<domain>/README.md` — domain exists in code; `alembic/<domain>/` exists with **empty** `versions/`; **not** in `ALEMBIC_DOMAINS` until first revision; **no** DB schema object until a migration creates it.
|
|
175
|
+
- `CHANGELOG.md` — `### Added` under `[Unreleased]` for domain package, `schema_<domain>`, and Alembic skeleton path (optional). **Do not** list `ALEMBIC_DOMAINS` or revision ids until the first migration lands.
|
|
176
|
+
- Update `docs/architecture.md` and `docs/package-api.md` only when the domain exposes anything to the public surface.
|
|
177
|
+
|
|
178
|
+
## Follow-up: first revision + enroll in `cf-alembic`
|
|
179
|
+
|
|
180
|
+
Do this in a **separate step/PR** when you have DDL to apply.
|
|
181
|
+
|
|
182
|
+
1. Add **`alembic/<domain>/alembic/versions/*.py`** (at least one revision); `upgrade()` may create schema/tables/enums as designed.
|
|
183
|
+
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).
|
|
184
|
+
1. `make alembic` and deploy’s `cf-alembic` step pick up the new domain automatically — no makefile or `_deploy.yml` edits needed.
|
|
185
|
+
|
|
186
|
+
`downgrade()` policy for baselines in this repo: raise `RuntimeError` when downgrades are unsupported, per existing `v1_*_init` migrations.
|
|
187
|
+
|
|
188
|
+
## What you do NOT need to touch
|
|
189
|
+
|
|
190
|
+
- `makefile` `alembic` target — uses `cf-alembic`.
|
|
191
|
+
- `.github/workflows/_deploy.yml` migration step — uses `cf-alembic`.
|
|
192
|
+
- `pyproject.toml` `[tool.hatch.build.targets.wheel.force-include] "alembic"` — already directory-level, ships new trees automatically.
|
|
193
|
+
|
|
194
|
+
## Drift safety
|
|
195
|
+
|
|
196
|
+
`tests/unit/core/bundled_alembic_test.py` exercises `iter_alembic_ini_paths()` over **only** `ALEMBIC_DOMAINS`. A scaffolded domain with an empty `versions/` folder is **invisible** to that test until it is appended — which must happen **together** with the first revision.
|
|
197
|
+
|
|
198
|
+
## Reminders
|
|
199
|
+
|
|
200
|
+
- One step per response, wait for review (`implementation-workflow`).
|
|
201
|
+
- Domain imports follow `domain-imports`; do not import API/application from domain.
|
|
202
|
+
- Never name domain symbols by caller (`Admin*`, `Api*`, `Worker*`) per `domain-caller-context`.
|
|
203
|
+
- When adding real repository methods later, follow `repository-read-consistency` (`_strong` naming where applicable).
|
|
@@ -120,47 +120,12 @@ jobs:
|
|
|
120
120
|
FIREBASE_CONFIG_PATH="$FIREBASE_CONFIG_PATH" \
|
|
121
121
|
docker compose -f "$COMPOSE_FILE" pull
|
|
122
122
|
|
|
123
|
-
echo "Running database migrations (
|
|
123
|
+
echo "Running database migrations (cf-alembic iterates core_framework.bundled_alembic.ALEMBIC_DOMAINS in order)"
|
|
124
124
|
docker run --rm \
|
|
125
125
|
--network host \
|
|
126
126
|
-v "$CONFIG_PATH:/app/config.toml:ro" \
|
|
127
127
|
"$DOCKERHUB_USERNAME/core-framework:latest" \
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
echo "Running database migrations (user)"
|
|
131
|
-
docker run --rm \
|
|
132
|
-
--network host \
|
|
133
|
-
-v "$CONFIG_PATH:/app/config.toml:ro" \
|
|
134
|
-
"$DOCKERHUB_USERNAME/core-framework:latest" \
|
|
135
|
-
alembic -c /app/alembic/user/alembic.ini upgrade head
|
|
136
|
-
|
|
137
|
-
echo "Running database migrations (moderation)"
|
|
138
|
-
docker run --rm \
|
|
139
|
-
--network host \
|
|
140
|
-
-v "$CONFIG_PATH:/app/config.toml:ro" \
|
|
141
|
-
"$DOCKERHUB_USERNAME/core-framework:latest" \
|
|
142
|
-
alembic -c /app/alembic/moderation/alembic.ini upgrade head
|
|
143
|
-
|
|
144
|
-
echo "Running database migrations (post)"
|
|
145
|
-
docker run --rm \
|
|
146
|
-
--network host \
|
|
147
|
-
-v "$CONFIG_PATH:/app/config.toml:ro" \
|
|
148
|
-
"$DOCKERHUB_USERNAME/core-framework:latest" \
|
|
149
|
-
alembic -c /app/alembic/post/alembic.ini upgrade head
|
|
150
|
-
|
|
151
|
-
echo "Running database migrations (comment)"
|
|
152
|
-
docker run --rm \
|
|
153
|
-
--network host \
|
|
154
|
-
-v "$CONFIG_PATH:/app/config.toml:ro" \
|
|
155
|
-
"$DOCKERHUB_USERNAME/core-framework:latest" \
|
|
156
|
-
alembic -c /app/alembic/comment/alembic.ini upgrade head
|
|
157
|
-
|
|
158
|
-
echo "Running database migrations (notification)"
|
|
159
|
-
docker run --rm \
|
|
160
|
-
--network host \
|
|
161
|
-
-v "$CONFIG_PATH:/app/config.toml:ro" \
|
|
162
|
-
"$DOCKERHUB_USERNAME/core-framework:latest" \
|
|
163
|
-
alembic -c /app/alembic/notification/alembic.ini upgrade head
|
|
128
|
+
cf-alembic
|
|
164
129
|
|
|
165
130
|
echo "Starting services with Docker Compose"
|
|
166
131
|
DOCKERHUB_USERNAME="$DOCKERHUB_USERNAME" \
|
|
@@ -168,6 +133,7 @@ jobs:
|
|
|
168
133
|
FIREBASE_CONFIG_PATH="$FIREBASE_CONFIG_PATH" \
|
|
169
134
|
docker compose -f "$COMPOSE_FILE" up -d \
|
|
170
135
|
--remove-orphans \
|
|
136
|
+
--force-recreate \
|
|
171
137
|
--scale worker="${{ inputs.worker_replicas }}"
|
|
172
138
|
|
|
173
139
|
echo "Cleaning up dangling Docker images"
|
|
@@ -79,6 +79,7 @@ jobs:
|
|
|
79
79
|
with:
|
|
80
80
|
context: .
|
|
81
81
|
file: ./dockerfile
|
|
82
|
+
target: api
|
|
82
83
|
push: true
|
|
83
84
|
tags: ${{ steps.meta.outputs.tags }}
|
|
84
85
|
|
|
@@ -110,7 +111,8 @@ jobs:
|
|
|
110
111
|
uses: docker/build-push-action@v6
|
|
111
112
|
with:
|
|
112
113
|
context: .
|
|
113
|
-
file: ./dockerfile
|
|
114
|
+
file: ./dockerfile
|
|
115
|
+
target: worker
|
|
114
116
|
push: true
|
|
115
117
|
tags: ${{ steps.meta.outputs.tags }}
|
|
116
118
|
|
|
@@ -100,6 +100,7 @@ jobs:
|
|
|
100
100
|
with:
|
|
101
101
|
context: .
|
|
102
102
|
file: ./dockerfile
|
|
103
|
+
target: api
|
|
103
104
|
push: false
|
|
104
105
|
tags: core-framework:ci
|
|
105
106
|
|
|
@@ -107,6 +108,7 @@ jobs:
|
|
|
107
108
|
uses: docker/build-push-action@v6
|
|
108
109
|
with:
|
|
109
110
|
context: .
|
|
110
|
-
file: ./dockerfile
|
|
111
|
+
file: ./dockerfile
|
|
112
|
+
target: worker
|
|
111
113
|
push: false
|
|
112
114
|
tags: core-framework-worker:ci
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable changes to **core-framework** (import **`core_framework`**). Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning is [SemVer](https://semver.org/spec/v2.0.0.html). Stable surface: **`docs/package-api.md`**.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [1.2.0] - 2026-05-02
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **Breaking:** Caching is **cashews-only** — removed **`RedisCache`**, **`core_framework.core.redis_cache`**, legacy **`@cache`**, **`redis_cache_status`** on readiness; **`invalidate_cache`** targets cashews keys only.
|
|
12
|
+
- **Breaking:** Workers use **ARQ** (not SAQ). Run **`arq core_framework.worker.main.WorkerSettings`**; tests use **`TestConfig.include_arq_fixtures`**. Queue name pinned to **`core_framework:queue`** (**`CORE_FRAMEWORK_QUEUE_NAME`**).
|
|
13
|
+
- **Breaking:** **`WorkerLifecycleContext`** merged into **`WorkerJobContext`**; **`worker_context`** exports **`WorkerStartupContext`** and **`WorkerJobContext`** only.
|
|
14
|
+
|
|
15
|
+
### Internal
|
|
16
|
+
|
|
17
|
+
- Worker startup defers cashews until **`startup`** (fixes pytest + Testcontainers ordering).
|
|
18
|
+
- Shared idempotent **`init_firebase(settings)`** for API lifespan and worker.
|
|
19
|
+
- Worker schedules/tasks use **`logger.exception`** for tracebacks.
|
|
20
|
+
|
|
21
|
+
### Documentation
|
|
22
|
+
|
|
23
|
+
- Follow feature (HTTP, notifications, stats worker, mirrors, **`docs/follow-system-design.md`**, architecture decisions, domain READMEs, **`docs/api.md`**).
|
|
24
|
+
|
|
25
|
+
## [1.1.1] - 2026-04-24
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **`core_framework.testing`:** configurable ASGI lifespan timeouts for pytest / HTTP client fixtures.
|
|
30
|
+
|
|
31
|
+
### Internal
|
|
32
|
+
|
|
33
|
+
- **`uv.lock`** refresh.
|
|
34
|
+
|
|
35
|
+
## [1.1.0] - 2026-04-22
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- **`core-framework[testing]`** extra: pytest plugin (**`pytest11`** → **`core_framework.testing.plugin`**), **`TestConfig`**, session fixtures (Postgres, Redis, **`AsyncClient`**, migrations, Firebase helpers, optional SAQ). Parallel (**`pytest -n`**) coordination — see **`docs/testing-plugin-design.md`**. **`create_firebase_user_token`** helper.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- **Breaking (bootstrap):** **`core_framework.asgi:app`** is **`init_app(load_default_settings())`** at import; **`core_framework.main`** no longer exposes module-level **`app`**. **`core_framework.core`** no longer re-exports **`settings`** — use **`core_framework.core.settings`**.
|
|
44
|
+
|
|
45
|
+
### Documentation
|
|
46
|
+
|
|
47
|
+
- **`docs/package-api.md`**, **`docs/core-framework-migration.md`**, **`README.md`**, **`docs/testing-plugin-design.md`** updated for testing and entrypoints.
|
|
48
|
+
|
|
49
|
+
## [1.0.0] - 2026-04-18
|
|
50
|
+
|
|
51
|
+
First **SemVer-stable** release per **`docs/package-api.md`**.
|
|
52
|
+
|
|
53
|
+
### Breaking
|
|
54
|
+
|
|
55
|
+
- **Alembic collapsed to six baselines** (`v1_ext_init` … `v1_notif_init`; order **`ALEMBIC_DOMAINS`**). **Fresh DB:** unchanged. **Already on pre-1.0 heads:** per-schema **`alembic stamp`** to the matching **`v1_*_init`** baseline before upgrading; **no supported downgrade** (baselines’ **`downgrade`** raises).
|
|
56
|
+
- Pre-1.0 revision IDs in older notes are **historical** only (files removed).
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
- Index tweaks in baselines (e.g. author/blocker lookup indexes; drops of unused indexes) — see git history for DDL detail.
|
|
61
|
+
|
|
62
|
+
### Removed
|
|
63
|
+
|
|
64
|
+
- Tests no longer **downgrade-to-base** per schema (empty DB + baselines only).
|
|
65
|
+
|
|
66
|
+
## [0.5.0] - 2026-04-17
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- **`author_context.is_notification_muted`** on post/comment when viewer is author. **`actor_id` NOT NULL** on user/moderation history (migration + backfill). **`user_restrictions_lookup`** in user schema + mirror sync with post/comment.
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
|
|
74
|
+
- **`GET /notifications`:** **`actor`** object replaces top-level **`actor_id`**. History endpoints require **`actor`**. **`Postgres.get_transaction_with_actor_id`** supports explicit **`actor_id`**.
|
|
75
|
+
|
|
76
|
+
### Documentation
|
|
77
|
+
|
|
78
|
+
- Mentions flow, **`GET /users/usernames/suggest`**, **`docs/api.md`**.
|
|
79
|
+
|
|
80
|
+
## [0.4.0] - 2026-03-26
|
|
81
|
+
|
|
82
|
+
### Added
|
|
83
|
+
|
|
84
|
+
- Bundled Alembic in wheel (**`core_framework/alembic/`**), **`core_framework.bundled_alembic`**, console script **`cf-alembic`**.
|
|
85
|
+
|
|
86
|
+
### Changed
|
|
87
|
+
|
|
88
|
+
- Script renamed from **`cff-migrate`** to **`cf-alembic`**. **`pyproject`**: dropped **`readme`** from **`[project]`** for lean Docker builds. OpenAPI title **`core-framework API`**. **`main.app`** / worker **`task_worker`** import-time behavior adjusted (SAQ). Application DI: domain modules as namespaces; **`configure_application_dependencies`**; cashews wiring.
|
|
89
|
+
|
|
90
|
+
### Fixed
|
|
91
|
+
|
|
92
|
+
- **Worker Docker image:** builder copies **`alembic/`** for Hatch force-include.
|
|
93
|
+
|
|
94
|
+
### CI
|
|
95
|
+
|
|
96
|
+
- **package-smoke** and **docker-build** on PRs; Docker Hub image name **`core-framework`**.
|
|
97
|
+
|
|
98
|
+
### Documentation
|
|
99
|
+
|
|
100
|
+
- **`README`**, **`docs/core-framework-migration.md`**, **`docs/package-api.md`**, **`docs/architecture.md`** (runtime / bootstrap).
|
|
101
|
+
|
|
102
|
+
### Removed
|
|
103
|
+
|
|
104
|
+
- **`load_default_runtime()`**, **`DependencyHolder`**, **`core_framework.core.runtime`** package attribute shadowing the runtime module.
|
|
105
|
+
|
|
106
|
+
## [0.3.0] - 2026-03-25
|
|
107
|
+
|
|
108
|
+
### Added
|
|
109
|
+
|
|
110
|
+
- **`init_app(settings)`**, **`create_task_worker(settings)`**, **`CoreRuntime`**, **`build_core_runtime`**, **`configure_application_dependencies`**, **`load_default_settings()`**, **`configure_core_runtime`**. **`docs/package-api.md`**.
|
|
111
|
+
|
|
112
|
+
### Changed
|
|
113
|
+
|
|
114
|
+
- Embedding: prefer full bootstrap via **`init_app`**; **`/readiness`** needs **`app.state.core_runtime`**. Pagination, event tokens, media URLs, cache fail fast if not configured.
|
|
115
|
+
|
|
116
|
+
### Migration
|
|
117
|
+
|
|
118
|
+
- Host: **`Settings`** from host config → **`init_app(settings)`**; **`configure_core_runtime(runtime)`** if using **`core_framework.core`** globals directly.
|
|
119
|
+
|
|
120
|
+
[0.3.0]: https://github.com/NepNepFFXIV/core-framework/compare/v0.2.0...v0.3.0
|
|
121
|
+
[0.4.0]: https://github.com/NepNepFFXIV/core-framework/compare/v0.3.0...v0.4.0
|
|
122
|
+
[0.5.0]: https://github.com/NepNepFFXIV/core-framework/compare/v0.4.0...v0.5.0
|
|
123
|
+
[1.0.0]: https://github.com/NepNepFFXIV/core-framework/compare/v0.5.0...v1.0.0
|
|
124
|
+
[1.1.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.0.0...v1.1.0
|
|
125
|
+
[1.1.1]: https://github.com/NepNepFFXIV/core-framework/compare/v1.1.0...v1.1.1
|
|
126
|
+
[1.2.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.1.1...v1.2.0
|
|
127
|
+
[unreleased]: https://github.com/NepNepFFXIV/core-framework/compare/v1.2.0...HEAD
|