core-framework 1.3.0__tar.gz → 1.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- core_framework-1.5.0/.cursor/rules/code-review-output.mdc +31 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/constants-final.mdc +1 -1
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/database-triggers.md +1 -24
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/domain-imports.mdc +17 -1
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/domain-input-guards.mdc +3 -2
- core_framework-1.5.0/.cursor/rules/domain-services-and-adapters.mdc +47 -0
- core_framework-1.5.0/.cursor/rules/exception-handlers.mdc +33 -0
- core_framework-1.5.0/.cursor/rules/henry-cursor-rules-sync.mdc +52 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/implementation-workflow.mdc +5 -5
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/layer-boundaries.mdc +4 -2
- core_framework-1.5.0/.cursor/rules/structured-logging.mdc +35 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/skills/add-domain/SKILL.md +2 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/skills/code-review/SKILL.md +10 -1
- {core_framework-1.3.0 → core_framework-1.5.0}/.dockerignore +2 -3
- {core_framework-1.3.0 → core_framework-1.5.0}/.github/workflows/_deploy.yml +50 -5
- {core_framework-1.3.0 → core_framework-1.5.0}/.gitignore +8 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/CHANGELOG.md +65 -1
- {core_framework-1.3.0 → core_framework-1.5.0}/PKG-INFO +4 -1
- core_framework-1.5.0/alembic/media/alembic/env.py +72 -0
- core_framework-1.5.0/alembic/media/alembic/versions/v1_media_init_baseline.py +45 -0
- core_framework-1.5.0/alembic/notification/alembic.ini +52 -0
- core_framework-1.5.0/alembic/user/alembic/README +1 -0
- core_framework-1.5.0/alembic/user/alembic/script.py.mako +28 -0
- core_framework-1.5.0/alembic/user/alembic/versions/user_add_avatar_ingest_sequences.py +34 -0
- core_framework-1.5.0/alembic/user/alembic/versions/user_add_date_of_birth_to_profiles.py +27 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/config.toml +16 -4
- {core_framework-1.3.0 → core_framework-1.5.0}/config.toml.template +16 -4
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/admin/users/router.py +2 -2
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/admin/users/schemas.py +8 -6
- core_framework-1.5.0/core_framework/api/comments/authenticated/mappers.py +10 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/comments/authenticated/router.py +2 -2
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/dependencies.py +11 -1
- core_framework-1.5.0/core_framework/api/posts/authenticated/mappers.py +14 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/posts/authenticated/router.py +2 -2
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/posts/authenticated/schemas.py +3 -2
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/posts/public/router.py +11 -3
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/posts/schemas.py +0 -1
- core_framework-1.5.0/core_framework/api/users/authenticated/mappers.py +21 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/users/authenticated/router.py +72 -7
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/users/authenticated/schemas.py +10 -2
- core_framework-1.5.0/core_framework/api/users/mappers.py +72 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/users/shared/schemas.py +35 -6
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/bootstrap.py +4 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/comments/authenticated_service.py +3 -9
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/comments/public_service.py +91 -52
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/events/event_service.py +2 -2
- core_framework-1.5.0/core_framework/application/media/README.md +11 -0
- core_framework-1.5.0/core_framework/application/media/avatar_service.py +148 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/moderation/appeal_service.py +10 -15
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/moderation/user_service.py +41 -38
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/posts/authenticated_service.py +3 -9
- core_framework-1.5.0/core_framework/application/shared/enums.py +21 -0
- core_framework-1.5.0/core_framework/application/shared/worker_jobs.py +17 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/users/admin_service.py +21 -14
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/users/authenticated_service.py +21 -23
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/bundled_alembic.py +1 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/exception_handlers/comment.py +10 -25
- core_framework-1.5.0/core_framework/core/exception_handlers/common.py +19 -0
- core_framework-1.5.0/core_framework/core/exception_handlers/media.py +64 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/exception_handlers/moderation.py +10 -30
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/exception_handlers/notification.py +5 -5
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/exception_handlers/post.py +7 -13
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/exception_handlers/setup.py +8 -24
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/exception_handlers/user.py +8 -20
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/redis.py +5 -1
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/runtime.py +3 -3
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/settings.py +81 -11
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/comment/__init__.py +2 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/comment/dependencies.py +2 -2
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/comment/models.py +32 -1
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/comment/repository.py +14 -5
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/comment/service.py +7 -8
- core_framework-1.5.0/core_framework/domains/media/README.md +25 -0
- core_framework-1.5.0/core_framework/domains/media/__init__.py +30 -0
- core_framework-1.5.0/core_framework/domains/media/avatar/components/finals_publisher.py +16 -0
- core_framework-1.5.0/core_framework/domains/media/avatar/components/staging.py +51 -0
- core_framework-1.5.0/core_framework/domains/media/avatar/components/variant_encoder.py +15 -0
- core_framework-1.5.0/core_framework/domains/media/avatar/constants.py +26 -0
- core_framework-1.5.0/core_framework/domains/media/avatar/exceptions.py +26 -0
- core_framework-1.5.0/core_framework/domains/media/avatar/models.py +24 -0
- core_framework-1.5.0/core_framework/domains/media/avatar/ports/finals_publisher.py +7 -0
- core_framework-1.5.0/core_framework/domains/media/avatar/ports/staging.py +10 -0
- core_framework-1.5.0/core_framework/domains/media/avatar/ports/variant_encoder.py +7 -0
- core_framework-1.5.0/core_framework/domains/media/avatar/service.py +162 -0
- core_framework-1.5.0/core_framework/domains/media/dependencies.py +82 -0
- core_framework-1.5.0/core_framework/domains/media/shared/exceptions.py +6 -0
- core_framework-1.5.0/core_framework/domains/media/shared/repository.py +179 -0
- core_framework-1.5.0/core_framework/domains/media/shared/service.py +6 -0
- core_framework-1.5.0/core_framework/domains/media/shared/utils.py +21 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/moderation/dependencies.py +2 -2
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/moderation/service.py +4 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/notification/dependencies.py +2 -2
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/notification/repository.py +8 -6
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/notification/service.py +2 -2
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/post/__init__.py +2 -1
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/post/constants.py +2 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/post/dependencies.py +2 -2
- core_framework-1.5.0/core_framework/domains/post/models.py +98 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/post/repository.py +102 -20
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/post/service.py +25 -13
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/user/__init__.py +8 -0
- core_framework-1.5.0/core_framework/domains/user/constants.py +11 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/user/dependencies.py +2 -2
- core_framework-1.5.0/core_framework/domains/user/models.py +273 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/user/repository.py +89 -32
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/user/service.py +37 -38
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/utils.py +0 -13
- core_framework-1.5.0/core_framework/infrastructure/media/local_staging_adapter.py +61 -0
- core_framework-1.5.0/core_framework/infrastructure/media/pillow_variant_encoder_adapter.py +45 -0
- core_framework-1.5.0/core_framework/infrastructure/media/ssh_finals_publisher_adapter.py +101 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/main.py +4 -4
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/testing/arq.py +2 -1
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/testing/auth.py +37 -0
- core_framework-1.5.0/core_framework/testing/media.py +81 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/worker/main.py +4 -3
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/worker/schedules/__init__.py +10 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/worker/schedules/schedule_aggregate_comment_stats.py +8 -5
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/worker/schedules/schedule_aggregate_post_stats.py +6 -5
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/worker/schedules/schedule_aggregate_user_stats.py +6 -5
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/worker/schedules/schedule_expired_account_deletions.py +4 -3
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/worker/schedules/schedule_expired_mute_lifts.py +4 -3
- core_framework-1.5.0/core_framework/worker/schedules/schedule_sweep_stale_avatar_staging.py +14 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/worker/tasks/__init__.py +4 -0
- core_framework-1.5.0/core_framework/worker/tasks/delete_superseded_avatar_finals.py +16 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/worker/tasks/process_account_deletion.py +1 -1
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/worker/tasks/process_aggregate_comment_stats.py +2 -2
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/worker/tasks/process_aggregate_post_stats.py +2 -2
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/worker/tasks/process_aggregate_user_stats.py +2 -2
- core_framework-1.5.0/core_framework/worker/tasks/process_media_asset_variants.py +31 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/worker/tasks/process_mute_lift.py +1 -1
- {core_framework-1.3.0 → core_framework-1.5.0}/docker-compose.yml +4 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/dockerfile +1 -1
- core_framework-1.5.0/docs/README.md +22 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/api.md +1 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/architecture.md +13 -5
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/conventions.md +9 -1
- core_framework-1.5.0/docs/deployment/Caddyfile.example +24 -0
- core_framework-1.5.0/docs/deployment/edge-upload-limits.md +59 -0
- core_framework-1.5.0/docs/deployment/image-server-known-hosts.example +5 -0
- core_framework-1.5.0/docs/deployment/image-server-ssh.md +84 -0
- core_framework-1.5.0/docs/domain-services-and-adapters.md +179 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/comments/retrieve_comments.md +4 -2
- core_framework-1.5.0/docs/flows/media/upload_pipeline.md +37 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/notifications/notification_inbox.md +1 -1
- core_framework-1.5.0/docs/flows/posts/post_visibility.md +75 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/users/account.md +14 -3
- core_framework-1.5.0/docs/flows/users/avatar.md +94 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/users/change_history.md +2 -1
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/users/my_posts_and_comments.md +9 -4
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/users/preferences.md +4 -4
- core_framework-1.5.0/docs/flows/users/profile.md +142 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/follow-system-design.md +5 -3
- core_framework-1.5.0/docs/media-upload-pipeline-design.md +459 -0
- core_framework-1.5.0/docs/patch-update-typed-payload.md +186 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/testing-plugin-design.md +8 -9
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/todo.md +2 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/pyproject.toml +4 -1
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/conftest.py +10 -2
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/api/admin/users/router_test.py +52 -12
- core_framework-1.5.0/tests/integration/api/users/authenticated/avatar_upload_test.py +165 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/api/users/authenticated/router_test.py +160 -34
- core_framework-1.5.0/tests/integration/api/users/public/__init__.py +0 -0
- core_framework-1.5.0/tests/integration/worker/__init__.py +0 -0
- core_framework-1.5.0/tests/integration/worker/avatar_upload_test.py +167 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/worker/conftest.py +1 -1
- core_framework-1.5.0/tests/unit/__init__.py +0 -0
- core_framework-1.5.0/tests/unit/application/comments/__init__.py +0 -0
- core_framework-1.5.0/tests/unit/domains/__init__.py +0 -0
- core_framework-1.5.0/tests/unit/domains/comment/__init__.py +0 -0
- core_framework-1.5.0/tests/unit/infrastructure/__init__.py +0 -0
- core_framework-1.5.0/tests/unit/infrastructure/media/__init__.py +0 -0
- core_framework-1.5.0/tests/unit/infrastructure/media/ssh_finals_publisher_adapter_test.py +279 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/uv.lock +62 -1
- core_framework-1.3.0/core_framework/application/shared/enums.py +0 -11
- core_framework-1.3.0/core_framework/core/exception_handlers/common.py +0 -5
- core_framework-1.3.0/core_framework/domains/post/models.py +0 -61
- core_framework-1.3.0/core_framework/domains/user/constants.py +0 -41
- core_framework-1.3.0/core_framework/domains/user/models.py +0 -153
- core_framework-1.3.0/docs/flows/users/profile.md +0 -90
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/api-layer.mdc +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/api-reference-docs.mdc +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/api-security.mdc +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/api-validation.mdc +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/application-layer.mdc +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/domain-caller-context.mdc +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/domain-repository-exceptions.mdc +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/flow-documentation.mdc +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/integration-test-strategy.mdc +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/no-code-in-docs.mdc +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/no-docstrings.mdc +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/postgres-config-conventions.mdc +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/repository-read-consistency.mdc +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/strong-read-opt-in.mdc +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/rules/tech-stack.mdc +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/skills/add-config/SKILL.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.cursor/skills/recommend-features/SKILL.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.github/workflows/dev-ci-cd.yaml +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.github/workflows/manual-deployment.yaml +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.github/workflows/publish-pypi.yml +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.github/workflows/test.yaml +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.pre-commit-config.yaml +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/.python-version +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/LICENSE +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/README.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/comment/alembic/README +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/comment/alembic/env.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/comment/alembic/script.py.mako +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/comment/alembic/versions/v1_comment_init_baseline.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/comment/alembic.ini +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/extension/alembic/README +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/extension/alembic/env.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/extension/alembic/script.py.mako +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/extension/alembic/versions/v1_ext_init_baseline.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/extension/alembic.ini +0 -0
- {core_framework-1.3.0/alembic/moderation → core_framework-1.5.0/alembic/media}/alembic/README +0 -0
- {core_framework-1.3.0/alembic/moderation → core_framework-1.5.0/alembic/media}/alembic/script.py.mako +0 -0
- {core_framework-1.3.0/alembic/notification → core_framework-1.5.0/alembic/media}/alembic.ini +0 -0
- {core_framework-1.3.0/alembic/notification → core_framework-1.5.0/alembic/moderation}/alembic/README +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/moderation/alembic/env.py +0 -0
- {core_framework-1.3.0/alembic/notification → core_framework-1.5.0/alembic/moderation}/alembic/script.py.mako +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/moderation/alembic/versions/v1_mod_init_baseline.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/moderation/alembic.ini +0 -0
- {core_framework-1.3.0/alembic/post → core_framework-1.5.0/alembic/notification}/alembic/README +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/notification/alembic/env.py +0 -0
- {core_framework-1.3.0/alembic/post → core_framework-1.5.0/alembic/notification}/alembic/script.py.mako +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/notification/alembic/versions/v1_notif_init_baseline.py +0 -0
- {core_framework-1.3.0/alembic/user → core_framework-1.5.0/alembic/post}/alembic/README +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/post/alembic/env.py +0 -0
- {core_framework-1.3.0/alembic/user → core_framework-1.5.0/alembic/post}/alembic/script.py.mako +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/post/alembic/versions/v1_post_init_baseline.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/post/alembic.ini +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/user/alembic/env.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/user/alembic/versions/v1_user_init_baseline.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/alembic/user/alembic.ini +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/admin/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/admin/comments/router.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/admin/comments/schemas.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/admin/moderation/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/admin/moderation/router.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/admin/moderation/schemas.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/admin/posts/router.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/admin/posts/schemas.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/admin/router.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/admin/users/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/auth/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/auth/router.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/auth/schemas.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/comments/authenticated/schemas.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/comments/public/router.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/comments/public/schemas.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/comments/router.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/comments/schemas.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/constants.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/events/router.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/events/schemas.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/notifications/authenticated/router.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/notifications/authenticated/schemas.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/notifications/router.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/posts/public/schemas.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/posts/router.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/router.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/schemas.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/system/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/system/router.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/users/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/users/authenticated/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/users/public/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/users/public/router.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/users/public/schemas.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/api/users/router.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/auth/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/auth/access_service.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/auth/auth_service.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/auth/models.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/comments/admin_service.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/comments/aggregation_service.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/events/README.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/events/event_token.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/events/models.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/moderation/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/moderation/moderator_service.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/moderation/report_service.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/moderation/scheduled_service.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/notifications/README.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/notifications/enums.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/notifications/inbox_service.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/notifications/mute_service.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/notifications/notification_service.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/posts/admin_service.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/posts/aggregation_service.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/posts/public_service.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/shared/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/shared/exceptions.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/shared/user_agent.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/users/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/users/aggregation_service.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/users/public_service.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/application/users/scheduled_service.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/asgi.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/constants.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/cache.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/context.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/database.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/exception_handlers/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/firebase.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/http_client.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/logging.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/middleware.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/observability.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/core/pagination.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/comment/README.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/comment/constants.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/comment/enums.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/comment/exceptions.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/exceptions.py +0 -0
- {core_framework-1.3.0/core_framework/worker → core_framework-1.5.0/core_framework/domains/media/avatar}/__init__.py +0 -0
- {core_framework-1.3.0/tests → core_framework-1.5.0/core_framework/domains/media/avatar/components}/__init__.py +0 -0
- {core_framework-1.3.0/tests/integration → core_framework-1.5.0/core_framework/domains/media/avatar/ports}/__init__.py +0 -0
- {core_framework-1.3.0/tests/integration/api → core_framework-1.5.0/core_framework/domains/media/shared}/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/moderation/README.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/moderation/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/moderation/enums.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/moderation/exceptions.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/moderation/models.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/moderation/repository.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/notification/README.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/notification/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/notification/enums.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/notification/exceptions.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/notification/models.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/post/README.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/post/enums.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/post/exceptions.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/user/README.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/user/enums.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/user/exceptions.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/domains/user/utils.py +0 -0
- {core_framework-1.3.0/tests/integration/api/admin → core_framework-1.5.0/core_framework/infrastructure}/__init__.py +0 -0
- {core_framework-1.3.0/tests/integration/api/admin/comments → core_framework-1.5.0/core_framework/infrastructure/media}/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/migrate_cli.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/testing/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/testing/config.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/testing/containers.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/testing/firebase.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/testing/hookspecs.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/testing/httpx_test_client.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/testing/migrations.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/testing/plugin.py +0 -0
- {core_framework-1.3.0/tests/integration/api/admin/moderation → core_framework-1.5.0/core_framework/worker}/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/core_framework/worker/worker_context.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docker-compose.dev.yaml +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/architecture-decisions.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/core-framework-migration.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/database-triggers.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/event-outbox-design.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/auth/access_control.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/auth/registration.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/comments/admin_comments.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/comments/comment_report.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/comments/comment_stats_aggregation.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/comments/create_comment.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/comments/delete_comment.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/comments/edit_comment.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/events/events.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/mentions/mentions_in_content.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/moderation/appeals.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/moderation/internal_notes.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/moderation/moderator_actions.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/moderation/reports.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/moderation/restrictions.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/posts/admin_posts.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/posts/author_context.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/posts/hashtag_discovery.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/posts/post_like.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/posts/post_stats_aggregation.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/users/account_deletion.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/users/blocks.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/users/check_username_exists.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/users/follow.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/flows/users/user_removal.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/docs/package-api.md +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/firebase_config.example.json +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/makefile +0 -0
- {core_framework-1.3.0/tests/integration/api/admin/posts → core_framework-1.5.0/tests}/__init__.py +0 -0
- {core_framework-1.3.0/tests/integration/api/admin/users → core_framework-1.5.0/tests/integration}/__init__.py +0 -0
- {core_framework-1.3.0/tests/integration/api/auth → core_framework-1.5.0/tests/integration/api}/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/api/_http_helpers.py +0 -0
- {core_framework-1.3.0/tests/integration/api/comments → core_framework-1.5.0/tests/integration/api/admin}/__init__.py +0 -0
- {core_framework-1.3.0/tests/integration/api/comments/public → core_framework-1.5.0/tests/integration/api/admin/comments}/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/api/admin/comments/router_test.py +0 -0
- {core_framework-1.3.0/tests/integration/api/notifications → core_framework-1.5.0/tests/integration/api/admin/moderation}/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/api/admin/moderation/router_test.py +0 -0
- {core_framework-1.3.0/tests/integration/api → core_framework-1.5.0/tests/integration/api/admin}/posts/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/api/admin/posts/router_test.py +0 -0
- {core_framework-1.3.0/tests/integration/api/posts/public → core_framework-1.5.0/tests/integration/api/admin/users}/__init__.py +0 -0
- {core_framework-1.3.0/tests/integration/api/system → core_framework-1.5.0/tests/integration/api/auth}/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/api/auth/router_test.py +0 -0
- {core_framework-1.3.0/tests/integration/api/users → core_framework-1.5.0/tests/integration/api/comments}/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/api/comments/authenticated/comment_writes_integration_test.py +0 -0
- {core_framework-1.3.0/tests/integration/api/users/authenticated → core_framework-1.5.0/tests/integration/api/comments/public}/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/api/comments/public/router_test.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/api/events/router_test.py +0 -0
- {core_framework-1.3.0/tests/integration/api/users/public → core_framework-1.5.0/tests/integration/api/notifications}/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/api/notifications/router_test.py +0 -0
- {core_framework-1.3.0/tests/integration/worker → core_framework-1.5.0/tests/integration/api/posts}/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/api/posts/authenticated/post_writes_integration_test.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/api/posts/comment_count_aggregation_test.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/api/posts/followers_visibility_test.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/api/posts/post_stats_dirty_marking_test.py +0 -0
- {core_framework-1.3.0/tests/unit → core_framework-1.5.0/tests/integration/api/posts/public}/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/api/posts/public/router_test.py +0 -0
- {core_framework-1.3.0/tests/unit/application/comments → core_framework-1.5.0/tests/integration/api/system}/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/api/system/router_test.py +0 -0
- {core_framework-1.3.0/tests/unit/domains → core_framework-1.5.0/tests/integration/api/users}/__init__.py +0 -0
- {core_framework-1.3.0/tests/unit/domains/comment → core_framework-1.5.0/tests/integration/api/users/authenticated}/__init__.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/api/users/public/router_test.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/worker/account_deletion_test.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/worker/aggregate_comment_stats_test.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/worker/aggregate_post_stats_test.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/worker/aggregate_user_stats_test.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/worker/mute_lift_test.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/integration/worker/utils_test.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/unit/application/events/event_service_test.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/unit/application/notifications/inbox_service_test.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/unit/core/bundled_alembic_test.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/unit/core/migrate_cli_test.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/unit/core/pagination_test.py +0 -0
- {core_framework-1.3.0 → core_framework-1.5.0}/tests/unit/domains/user/service_test.py +0 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: When the user asks for a code review or audit, report only issues and improvements—not strengths
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Code review output
|
|
7
|
+
|
|
8
|
+
When the user asks for a **review**, **audit**, or similar (e.g. "review my code", "comprehensive review", "what's wrong with"):
|
|
9
|
+
|
|
10
|
+
## Report only
|
|
11
|
+
|
|
12
|
+
- **Bugs** and correctness risks
|
|
13
|
+
- **Wrong** behavior vs design docs, API contracts, or project rules
|
|
14
|
+
- **Improvements** and **optimizations** worth making
|
|
15
|
+
- **Contradictions** between code and documented intent (including doc drift)
|
|
16
|
+
|
|
17
|
+
Prioritize by severity. Include enough context (file, behavior, suggested fix direction) to act on each item.
|
|
18
|
+
|
|
19
|
+
## Do not report
|
|
20
|
+
|
|
21
|
+
- Sections praising what is **solid**, **well done**, or **correctly implemented**
|
|
22
|
+
- **"What's good"** / **"Overall assessment"** summaries that restate strengths
|
|
23
|
+
- Long **checklist pass** narratives ("layering is clean", "DI is wired correctly") unless a listed item is actually broken
|
|
24
|
+
|
|
25
|
+
A one-line **scope** (what was reviewed) is fine. If nothing actionable is found, say so briefly—do not pad with compliments.
|
|
26
|
+
|
|
27
|
+
## Accepted trade-offs
|
|
28
|
+
|
|
29
|
+
Do **not** list documented accepted trade-offs as findings unless the user asked about them or the implementation **violates** the documented trade-off (then report the violation).
|
|
30
|
+
|
|
31
|
+
Follow the **code-review** skill for *how* to review (docs to read, checklists); this rule governs *what to include in the answer*.
|
|
@@ -9,7 +9,7 @@ alwaysApply: false
|
|
|
9
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
10
|
|
|
11
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. **`
|
|
12
|
+
- Prefer **`FrozenSet`**, **`frozenset` literals**, **`MappingProxyType`**, or immutable collections for grouped constants when mutation must be prevented — e.g. **`ProfileUpdate.UPDATE_COLUMNS`** on domain update types.
|
|
13
13
|
- **`Final`** is for **assignment** semantics (no rebinding): it does not freeze mutable object contents unless the type/container is immutable.
|
|
14
14
|
|
|
15
15
|
Do not refactor unrelated legacy constants only to add **`Final`** unless you are already touching that symbol.
|
|
@@ -124,30 +124,7 @@ $$ language plpgsql;
|
|
|
124
124
|
- Error message isn't localized
|
|
125
125
|
- Makes assumptions about business rules
|
|
126
126
|
|
|
127
|
-
**Correct approach:**
|
|
128
|
-
|
|
129
|
-
```python
|
|
130
|
-
# In service layer
|
|
131
|
-
async def change_profile(
|
|
132
|
-
self,
|
|
133
|
-
*,
|
|
134
|
-
actor_id: str,
|
|
135
|
-
user_id: str,
|
|
136
|
-
validated_update_request: dict[str, Any],
|
|
137
|
-
) -> None:
|
|
138
|
-
if "username" in validated_update_request:
|
|
139
|
-
recent_changes = await self.repository.select_recent_username_changes(
|
|
140
|
-
user_id=user_id, since=datetime.now(timezone.utc) - timedelta(days=7)
|
|
141
|
-
)
|
|
142
|
-
if recent_changes:
|
|
143
|
-
raise UsernameCooldownException()
|
|
144
|
-
|
|
145
|
-
await self.repository.update_profile(
|
|
146
|
-
user_id=user_id,
|
|
147
|
-
validated_update_request=validated_update_request,
|
|
148
|
-
actor_id=actor_id,
|
|
149
|
-
)
|
|
150
|
-
```
|
|
127
|
+
**Correct approach:** enforce cooldown (or other policy) in the service, then call the repository with a typed **`ProfileUpdate`** (or the relevant `*Update`).
|
|
151
128
|
|
|
152
129
|
### ❌ Bad: Automatic State Transitions
|
|
153
130
|
|
|
@@ -57,6 +57,15 @@ from core_framework.domains.user.service import UserService # ❌
|
|
|
57
57
|
4. **Consistency across adapters**: API and application layers follow the same domain import contract
|
|
58
58
|
5. **Dependency direction**: Prevents tight coupling to internal implementation details
|
|
59
59
|
|
|
60
|
+
## Domain `__init__.py` export policy
|
|
61
|
+
|
|
62
|
+
Export a symbol from a domain's **`__init__.py`** only when it is consumed **outside** that domain package — for example API, application, infrastructure adapters, exception handlers, or workers (via application services).
|
|
63
|
+
|
|
64
|
+
- **Do export**: exceptions, constants, and models that outer layers or adapters import.
|
|
65
|
+
- **Do not export**: persistence/registry shapes, repository row mappings, and other types used only inside **`domains/<name>/`** (repository, service, components). Import those from submodule paths within the domain.
|
|
66
|
+
|
|
67
|
+
When adding a new model or exception, default to **not** exporting; add to **`__init__.py`** only when an outer layer needs it.
|
|
68
|
+
|
|
60
69
|
## Available Domain Exports
|
|
61
70
|
|
|
62
71
|
### Moderation Domain (`from core_framework.domains.moderation import ...`)
|
|
@@ -72,11 +81,18 @@ from core_framework.domains.user.service import UserService # ❌
|
|
|
72
81
|
- **Service**: Use `user_service` from `dependencies.py`
|
|
73
82
|
- **Internal** (do not import from API layer): `DomainUserNotFoundException` — handled within domain; API uses `None` return values
|
|
74
83
|
|
|
84
|
+
### Media Domain (`from core_framework.domains.media import ...`)
|
|
85
|
+
- **Constants**: `AVATAR_INGEST_MAX_BYTES`, `AVATAR_INGEST_UPLOAD_CHUNK_BYTES`, `AVATAR_FINAL_VARIANT_128`, `AVATAR_FINAL_VARIANT_500`
|
|
86
|
+
- **Models**: `AvatarIngestRegistration`, `AvatarVariantBytes`
|
|
87
|
+
- **Exceptions**: `BaseMediaException`, `AvatarIngestBodyTooLargeException`, `AvatarIngestLeaseConflictException`, `AvatarIngestUnsupportedContentTypeException`, `AvatarStagingBlobNotFoundException`, `AvatarFinalsPublishException`
|
|
88
|
+
- **Service**: Use `avatar_service` / `media_service` from `dependencies.py`
|
|
89
|
+
- **Internal** (not in `__init__.py`): `AvatarStagingRegistryEntry` — repository and `AvatarService` only; application uses service methods, not the registry row type
|
|
90
|
+
|
|
75
91
|
## How to Fix Violations
|
|
76
92
|
|
|
77
93
|
If you need something that's not exported in `__init__.py`:
|
|
78
94
|
|
|
79
|
-
1. **Check if it should be public**: If
|
|
95
|
+
1. **Check if it should be public**: If an outer layer (API, application, infrastructure adapter, exception handler) needs it, add it to the domain's `__init__.py`. If only repository/service code inside the domain needs it, keep it internal.
|
|
80
96
|
2. **Reconsider the design**: If it's internal implementation, maybe the API layer shouldn't access it directly
|
|
81
97
|
3. **Use the service**: Most domain logic should be accessed through `{domain}_service` from `dependencies.py`
|
|
82
98
|
|
|
@@ -43,13 +43,14 @@ Use these across domains so validation stays consistent and call sites stay read
|
|
|
43
43
|
| **`validate_pagination_limit(limit=...)`** | Paginated reads: `limit` in `[1, MAX_PAGE_FETCH_SIZE]` (aligned with `core_framework.core.pagination`). |
|
|
44
44
|
| **`validate_pagination_offset(offset=...)`** | Paginated reads: `offset` must be **≥ 0** (aligned with `OffsetQueryParams`). |
|
|
45
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. |
|
|
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`**). |
|
|
47
46
|
|
|
48
47
|
Prefer **`require_non_blank_kwargs`** / **`require_non_blank_if_not_none`** over many repeated **`require_non_blank`** lines when a method checks several fields.
|
|
49
48
|
|
|
49
|
+
**PATCH partial updates:** domain `*Update` dataclasses (e.g. **`ProfileUpdate`**) expose **`require_has_updates()`** (non-empty body with at least one allowed field) and **`fields_set`** (which keys the client sent). API mappers build these from Pydantic **`model_fields_set`**; services call **`update.require_has_updates()`** before repository writes. Empty PATCH bodies remain rejected at the API via **`BasePatchRequest`**.
|
|
50
|
+
|
|
50
51
|
## Repository (thin data access; no duplicate validation)
|
|
51
52
|
|
|
52
|
-
- **Call path**: API and application code must not import repositories directly (see **`domain-imports.mdc`**); they use **`{domain}_service`** from **`dependencies.py`**.
|
|
53
|
+
- **Call path**: API and application code must not import repositories directly (see **`domain-imports.mdc`**); they use **`{domain}_service`** from **`dependencies.py`**. PATCH flows reach **`UserRepository.update_*`** (and post/comment equivalents) only through **`UserService.change_*`**, which runs **`require_has_updates()`** on the typed update—there is no supported bypass for empty or all-unknown PATCH bodies in product code.
|
|
53
54
|
- **Prefer no input-validation guards** in repositories (empty batches, non-blank strings, pagination limits, positive int IDs): the **domain service** owns those contracts. The repository runs SQL; **`WHERE … = any($1)`** with an empty array is valid in Postgres and returns no rows.
|
|
54
55
|
- Only add a repository guard for **SQL-specific** hazards that are awkward to express as service policy (rare); **do not** duplicate service-level “skip if empty” logic.
|
|
55
56
|
- **Do not** use the repository as the **main** home for product rules that belong in the service.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Domain bounded contexts may use multiple services and ports; application orchestrates; repositories stay Postgres-only
|
|
3
|
+
globs:
|
|
4
|
+
- core_framework/domains/**/*.py
|
|
5
|
+
- core_framework/application/**/*.py
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Domain Services and Adapters
|
|
9
|
+
|
|
10
|
+
See **`docs/domain-services-and-adapters.md`** for the full model. Apply when adding or changing domain packages and application orchestration.
|
|
11
|
+
|
|
12
|
+
## Application = orchestration
|
|
13
|
+
|
|
14
|
+
- **`core_framework/application/...`** sequences multi-step use cases shared by API and worker (call domain services, enqueue jobs, coordinate cross-domain steps).
|
|
15
|
+
- Do **not** move shared orchestration into API routers or worker handlers when it is reused across transports.
|
|
16
|
+
- Do **not** treat application modules as the long-term home for concrete filesystem/HTTP/SSH clients—use domain ports + components + adapters (below).
|
|
17
|
+
|
|
18
|
+
## Domain ≠ database only
|
|
19
|
+
|
|
20
|
+
- A bounded context owns **business rules and contracts** for all of its capabilities, not only its Postgres schema.
|
|
21
|
+
- **Repositories** remain **Postgres-only** (SQL in repository + row mapping).
|
|
22
|
+
- **Non-SQL I/O** uses **ports**, **components**, and **adapters**—not `*Repository`.
|
|
23
|
+
|
|
24
|
+
## Multiple services per domain
|
|
25
|
+
|
|
26
|
+
- Facade **`*Service`** is wired in `dependencies`; internal **`*Component`** classes wrap ports.
|
|
27
|
+
- Do not mix SQL, `aiofiles`, HTTP, or SSH in one class.
|
|
28
|
+
- Application resolves facade services via **`import ...dependencies as ..._deps`**.
|
|
29
|
+
|
|
30
|
+
## Ports, components, and adapters
|
|
31
|
+
|
|
32
|
+
- **Port** — `*Port` in `domains/<capability>/ports/`; no I/O imports.
|
|
33
|
+
- **Component** — `*Component` in `domains/<capability>/components/`; port + domain rules; composed by facade `*Service`.
|
|
34
|
+
- **Adapter** — `*Adapter` in `infrastructure/<domain>/`; transport prefix when multiple impls.
|
|
35
|
+
- Wire adapters only in `dependencies.py`; application uses facade `*_deps` services.
|
|
36
|
+
|
|
37
|
+
## Prohibited
|
|
38
|
+
|
|
39
|
+
- SQL and filesystem/HTTP/SSH in the same **repository** or god **service** class.
|
|
40
|
+
- Domain imports from `core_framework.api`, `core_framework.worker`, or `core_framework.application`.
|
|
41
|
+
- Infrastructure imports in domain modules except **`dependencies.py`** (existing rule).
|
|
42
|
+
|
|
43
|
+
## Media ingest (v1)
|
|
44
|
+
|
|
45
|
+
- **Layout:** `domains/media/shared/`, `avatar/ports/`, `avatar/components/`, `AvatarService`.
|
|
46
|
+
- **Avatar:** `media_deps.avatar_service` only (not individual components).
|
|
47
|
+
- **Cross-domain:** `avatar_ingest_sequence` / profile `avatar_id` via **user** domain; orchestration in `application/media/avatar_service`.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: FastAPI exception handler registration and typing conventions
|
|
3
|
+
globs:
|
|
4
|
+
- core_framework/core/exception_handlers/**/*.py
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Exception Handlers
|
|
8
|
+
|
|
9
|
+
Domain-specific HTTP exception handlers live under **`core_framework/core/exception_handlers/`**.
|
|
10
|
+
|
|
11
|
+
## Registration
|
|
12
|
+
|
|
13
|
+
- Register handlers with **`register_exception_handler`** from **`core_framework.core.exception_handlers.common`**.
|
|
14
|
+
- Do **not** call **`app.add_exception_handler`** directly from domain handler modules or **`setup.py`**.
|
|
15
|
+
- Starlette’s **`ExceptionHandler`** type is wider than narrowly typed handlers; the single **`ty: ignore[invalid-argument-type]`** belongs in **`common.register_exception_handler`** only.
|
|
16
|
+
|
|
17
|
+
## Handler functions
|
|
18
|
+
|
|
19
|
+
- One async handler per mapped exception type: **`(Request, ExcT) -> JSONResponse`**.
|
|
20
|
+
- Type **`exc`** as the **specific** domain exception (not bare **`Exception`**) so handler bodies stay narrow.
|
|
21
|
+
- Map domain exceptions to HTTP status codes; use **`exc.message`** in **`{"detail": ...}`** for client-facing errors.
|
|
22
|
+
- Register **specific** exceptions before domain **base** catch-alls (e.g. **`BasePostException`** last in that module).
|
|
23
|
+
|
|
24
|
+
## Module layout
|
|
25
|
+
|
|
26
|
+
- One module per domain (e.g. **`post.py`**, **`media.py`**) exporting **`add_*_exception_handlers(app)`**.
|
|
27
|
+
- **`setup.py`** registers cross-cutting handlers, then calls each domain **`add_*_exception_handlers`**.
|
|
28
|
+
- Base domain handlers return **`GENERIC_INTERNAL_SERVER_ERROR_DETAIL`** — not raw exception messages.
|
|
29
|
+
|
|
30
|
+
## Avoid
|
|
31
|
+
|
|
32
|
+
- Per-call-site **`ty: ignore[invalid-argument-type]`** or **`cast(ExceptionHandler, ...)`** on registrations.
|
|
33
|
+
- Business logic or repository calls inside handlers — translate exception → HTTP response only.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: When editing Cursor rules or skills in core-framework, sync applicable changes to henry-backend
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Henry cursor rules sync
|
|
7
|
+
|
|
8
|
+
**Henry backend** embeds **core-framework** and shares most **`.cursor/rules/`** and **`.cursor/skills/`** content. Treat both repos as one rules surface unless the user scopes work to core-framework only.
|
|
9
|
+
|
|
10
|
+
## When this applies
|
|
11
|
+
|
|
12
|
+
Whenever you **add, edit, or remove** a file under **`.cursor/rules/`** or **`.cursor/skills/`** in **this** repository — including new always-on rules, skill workflow updates, and convention changes from feature work (e.g. media/avatar).
|
|
13
|
+
|
|
14
|
+
## Required follow-up
|
|
15
|
+
|
|
16
|
+
1. **Sync to henry-backend** in the **same task** or as the **immediate next step** after the user accepts the core-framework rule change.
|
|
17
|
+
2. Diff by **basename** against **`henry-backend/.cursor/`** (sibling repo or second workspace root).
|
|
18
|
+
3. Mention in your response that henry-backend was synced, or that it still needs sync if you stopped at core-only per user scope.
|
|
19
|
+
|
|
20
|
+
## How to sync
|
|
21
|
+
|
|
22
|
+
- **Shared rules/skills** — port into henry-backend and **adapt** for the host:
|
|
23
|
+
- globs: **`core_framework/**`** → **`src/**`** where host code applies
|
|
24
|
+
- imports: **`core_framework....`** vs **`src....`**
|
|
25
|
+
- strong reads: **`strong-read-opt-in`** → **`host-strong-read-opt-in`**
|
|
26
|
+
- host boundaries: reference **`henry-host-context`**
|
|
27
|
+
- **Do not overwrite** henry-only files:
|
|
28
|
+
- **`.cursor/rules/henry-host-context.mdc`**
|
|
29
|
+
- **`.cursor/rules/host-strong-read-opt-in.mdc`**
|
|
30
|
+
- **`.cursor/skills/add-host-domain/SKILL.md`**
|
|
31
|
+
- **Henry-only equivalents** — if core adds a rule with core-only globs (e.g. **`domain-input-guards`** on **`core_framework/**`**), ensure henry has coverage for **`src/**`** via an existing or new host-scoped rule.
|
|
32
|
+
|
|
33
|
+
## Related docs
|
|
34
|
+
|
|
35
|
+
When a rule encodes a new convention, check whether **`docs/`** in both repos need the same story.
|
|
36
|
+
|
|
37
|
+
**Core `docs/` must stay host-agnostic** — no references to Henry, henry-backend, or other specific consuming applications. Use generic “host application” language only.
|
|
38
|
+
|
|
39
|
+
Sync to henry when core changes:
|
|
40
|
+
|
|
41
|
+
- **`docs/architecture.md`** → henry **`docs/architecture.md`** (Henry preamble + synced body; adapt `docs/flows/…` links in henry to **`docs/core-product-docs.md`**)
|
|
42
|
+
- **`docs/domain-services-and-adapters.md`** → update henry host summary; core remains canonical
|
|
43
|
+
- **`docs/conventions.md`**, **`docs/architecture-decisions.md`** → synced copies in henry when they exist in core (adapt flow-doc links in henry)
|
|
44
|
+
- **`docs/deployment/`**, **`docs/media-upload-pipeline-design.md`** → sync when shared deploy/media docs change (adapt flow links in henry)
|
|
45
|
+
|
|
46
|
+
**Do not** mirror **`docs/flows/`** into henry-backend. Henry maintains **`docs/core-product-docs.md`** as a pointer/index to core product documentation.
|
|
47
|
+
|
|
48
|
+
Do not overwrite henry-only docs (e.g. **`docs/henry-users-architecture.md`**, calculator architecture, **`docs/core-product-docs.md`**, **`docs/flows/henry/`**).
|
|
49
|
+
|
|
50
|
+
## User-triggered sync
|
|
51
|
+
|
|
52
|
+
Phrases like **"sync rules with henry"** mean: diff **`.cursor/rules/`** and **`.cursor/skills/`** between repos and port any missing or stale shared content.
|
|
@@ -36,17 +36,17 @@ After the user accepts database changes (or if there were none):
|
|
|
36
36
|
|
|
37
37
|
After the user accepts the API layer (or if there was no API layer):
|
|
38
38
|
|
|
39
|
-
- Implement the **Repository** for the domain layer.
|
|
39
|
+
- Implement the **Repository** for the domain layer (Postgres only).
|
|
40
40
|
- Include the **domain model** if data is retrieved from the database.
|
|
41
|
-
-
|
|
41
|
+
- If the feature needs non-SQL I/O (filesystem, HTTP, SSH), add **ports** in the domain and **adapter implementations** under `core_framework/infrastructure/<domain>/` per `docs/domain-services-and-adapters.md`—not in the repository.
|
|
42
42
|
- Wait for the user to review and accept before proceeding.
|
|
43
43
|
|
|
44
44
|
## 4. Service and Application Layer
|
|
45
45
|
|
|
46
|
-
After the user accepts the repository:
|
|
46
|
+
After the user accepts the repository (and any ports/adapters):
|
|
47
47
|
|
|
48
|
-
- Implement
|
|
49
|
-
- Implement the **application** layer.
|
|
48
|
+
- Implement **domain service(s)** (persistence and any port-backed collaborators); wire in `dependencies.py`.
|
|
49
|
+
- Implement the **application** layer (orchestration across domains and transports—no permanent home for raw I/O libraries).
|
|
50
50
|
- Wire everything together (replace 501 with real logic).
|
|
51
51
|
- Wait for the user to review and accept before proceeding.
|
|
52
52
|
|
|
@@ -14,16 +14,18 @@ Use this architecture split consistently:
|
|
|
14
14
|
- `core_framework/api/...` -> HTTP routers/schemas/dependencies only
|
|
15
15
|
- `core_framework/worker/...` -> schedules/tasks/job handlers only
|
|
16
16
|
- `core_framework/application/...` -> transport-agnostic orchestration shared by API + worker
|
|
17
|
-
- `core_framework/domains/...` -> core domain logic, models, repositories
|
|
17
|
+
- `core_framework/domains/...` -> core domain logic, models, repositories (and optional ports/adapters for non-SQL I/O — see `domain-services-and-adapters`)
|
|
18
18
|
|
|
19
19
|
## Rules
|
|
20
20
|
|
|
21
21
|
- API layer must not contain business orchestration that is shared with workers.
|
|
22
22
|
- Worker layer must not contain domain business rules; call application services only.
|
|
23
|
-
- Worker code must not import **`core_framework.domains`** packages (no direct domain repository/service usage from workers).
|
|
23
|
+
- Worker code must not import **`core_framework.domains`** packages (no direct domain repository/service usage from workers), **except** narrow **housekeeping cron** handlers that call a configured domain service facade (e.g. **`schedule_sweep_stale_avatar_staging`** → **`media_deps.avatar_service`**). Do not add new worker→domain shortcuts without the same documented housekeeping rationale.
|
|
24
24
|
- Application layer must not import FastAPI/Starlette HTTP types.
|
|
25
25
|
- Domain layer must not import API, worker, or application modules.
|
|
26
26
|
- Domain layer must not import global infrastructure except in `dependencies.py`.
|
|
27
|
+
- Repositories are Postgres-only; filesystem, HTTP, SSH, and similar I/O use domain ports/adapters (separate collaborators), not repository methods.
|
|
28
|
+
- Application orchestrates multi-step flows; do not fold shared API+worker orchestration into domain services to avoid “application” existing.
|
|
27
29
|
|
|
28
30
|
## Dependency Direction
|
|
29
31
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use concise English sentences for structlog event strings
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Structured logging (structlog)
|
|
7
|
+
|
|
8
|
+
Use **structlog** (`get_logger`, `logger.info`, `logger.exception`, etc.). The **first positional argument** is the **event** string (JSON `"event"` in non-local environments).
|
|
9
|
+
|
|
10
|
+
## Event strings
|
|
11
|
+
|
|
12
|
+
- Write the event as a **short English sentence** — readable in local console and in log viewers.
|
|
13
|
+
- Use **sentence case**. Prefer past tense for failures (**"Failed to …"**, **"… failed"**).
|
|
14
|
+
- Put **IDs, counts, and context** in **keyword fields**, not in the event string.
|
|
15
|
+
- Do **not** use snake_case machine identifiers as events (for example `process_media_asset_variants_failed`).
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
# ✅ Preferred
|
|
19
|
+
logger.info(
|
|
20
|
+
"Skipped variant processing: staging registry missing",
|
|
21
|
+
owner_user_id=owner_user_id,
|
|
22
|
+
asset_id=asset_id,
|
|
23
|
+
)
|
|
24
|
+
logger.exception("Avatar variant processing failed", asset_id=asset_id)
|
|
25
|
+
|
|
26
|
+
# ❌ Avoid
|
|
27
|
+
logger.info("process_media_asset_variants_skipped_missing_registry", asset_id=asset_id)
|
|
28
|
+
logger.exception("delete_superseded_avatar_finals_failed", asset_id=asset_id)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## When editing
|
|
32
|
+
|
|
33
|
+
- **New** log calls must use English event strings.
|
|
34
|
+
- When you **change** logging in a function or module, convert nearby snake_case events in that same touch to English.
|
|
35
|
+
- Do **not** repo-wide migrate unrelated legacy event names unless the task asks for it.
|
|
@@ -111,6 +111,8 @@ A single `class <Domain>Service:` whose **`__init__`** takes `<Domain>Repository
|
|
|
111
111
|
|
|
112
112
|
When real behavior is added later, apply `repository-read-consistency` (`/.cursor/rules/repository-read-consistency.mdc`) for reads and `_strong` naming.
|
|
113
113
|
|
|
114
|
+
If the bounded context needs filesystem, HTTP, or SSH, add **ports/adapters** or extra domain collaborators per `docs/domain-services-and-adapters.md` — do not put that I/O in `repository.py`.
|
|
115
|
+
|
|
114
116
|
### `__init__.py`
|
|
115
117
|
|
|
116
118
|
Export the **classes** other layers may import: base exception, `<Domain>Repository`, `<Domain>Service`. List them in `__all__` per `domain-imports`. Do **not** re-export module-level `*_repository` / `*_service` instances from `dependencies` here.
|
|
@@ -7,6 +7,14 @@ description: Review code for quality, architecture compliance, and best practice
|
|
|
7
7
|
|
|
8
8
|
When performing a code review, follow this workflow.
|
|
9
9
|
|
|
10
|
+
## Output format (required)
|
|
11
|
+
|
|
12
|
+
Apply **`.cursor/rules/code-review-output.mdc`**. The user wants **actionable findings only**:
|
|
13
|
+
|
|
14
|
+
- Report bugs, wrong behavior, improvements, optimizations, and doc/code contradictions.
|
|
15
|
+
- **Do not** include "what's solid", praise sections, or positive overall assessments.
|
|
16
|
+
- If nothing is wrong, say so in one sentence—no filler.
|
|
17
|
+
|
|
10
18
|
## How to Ask (for the user)
|
|
11
19
|
|
|
12
20
|
**Phrases that trigger this skill:** "review my code", "audit", "comprehensive review", "review comprehensively"
|
|
@@ -28,9 +36,10 @@ When performing a code review, follow this workflow.
|
|
|
28
36
|
Read these files **before** proceeding with the review:
|
|
29
37
|
|
|
30
38
|
1. **`docs/architecture.md`** – Domain boundaries, prohibited practices, and dependency rules
|
|
39
|
+
1. **`docs/domain-services-and-adapters.md`** – Application orchestration vs domain services, ports/adapters, multiple collaborators per bounded context
|
|
31
40
|
1. **`docs/conventions.md`** – Coding conventions, domain rules, caching, Redis, exception handling, API layer rules
|
|
32
41
|
1. **`docs/architecture-decisions.md`** – Intentional design choices; do not flag these as issues
|
|
33
|
-
1. **`.cursor/rules/api-layer.mdc`**, **`.cursor/rules/application-layer.mdc`**, **`.cursor/rules/domain-imports.mdc`**, **`.cursor/rules/domain-caller-context.mdc`** – Apply when reviewing API/application/domain code
|
|
42
|
+
1. **`.cursor/rules/api-layer.mdc`**, **`.cursor/rules/application-layer.mdc`**, **`.cursor/rules/domain-services-and-adapters.mdc`**, **`.cursor/rules/domain-imports.mdc`**, **`.cursor/rules/domain-caller-context.mdc`** – Apply when reviewing API/application/domain code
|
|
34
43
|
|
|
35
44
|
## Step 2: Key Context
|
|
36
45
|
|
|
@@ -39,10 +39,13 @@ jobs:
|
|
|
39
39
|
REDIS_HOST: ${{ secrets.REDIS_HOST }}
|
|
40
40
|
REDIS_PORT: ${{ secrets.REDIS_PORT }}
|
|
41
41
|
ALLOWED_HOSTS: ${{ secrets.ALLOWED_HOSTS }}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
AVATAR_HOST_DOMAIN: ${{ secrets.AVATAR_HOST_DOMAIN }}
|
|
43
|
+
AVATAR_DEFAULT_IMAGE: ${{ secrets.AVATAR_DEFAULT_IMAGE }}
|
|
44
|
+
BANNER_HOST_DOMAIN: ${{ secrets.BANNER_HOST_DOMAIN }}
|
|
45
|
+
BANNER_DEFAULT_IMAGE: ${{ secrets.BANNER_DEFAULT_IMAGE }}
|
|
46
|
+
IMAGE_SERVER_HOST: ${{ secrets.IMAGE_SERVER_HOST }}
|
|
47
|
+
IMAGE_SERVER_PORT: ${{ secrets.IMAGE_SERVER_PORT }}
|
|
48
|
+
IMAGE_SERVER_USER: ${{ secrets.IMAGE_SERVER_USER }}
|
|
46
49
|
LOGFIRE_TOKEN: ${{ secrets.LOGFIRE_TOKEN }}
|
|
47
50
|
CORS_ALLOWED_ORIGINS: ${{ secrets.CORS_ALLOWED_ORIGINS }}
|
|
48
51
|
run: |
|
|
@@ -86,6 +89,28 @@ jobs:
|
|
|
86
89
|
sys.exit(1)
|
|
87
90
|
PY
|
|
88
91
|
|
|
92
|
+
- name: Generate image_server_ssh_key from secret
|
|
93
|
+
env:
|
|
94
|
+
IMAGE_SERVER_SSH_PRIVATE_KEY: ${{ secrets.IMAGE_SERVER_SSH_PRIVATE_KEY }}
|
|
95
|
+
run: |
|
|
96
|
+
set -euo pipefail
|
|
97
|
+
|
|
98
|
+
echo "Generating image_server_ssh_key..."
|
|
99
|
+
printf '%s\n' "$IMAGE_SERVER_SSH_PRIVATE_KEY" > image_server_ssh_key
|
|
100
|
+
test -s image_server_ssh_key
|
|
101
|
+
chmod 600 image_server_ssh_key
|
|
102
|
+
|
|
103
|
+
- name: Generate image_server_known_hosts from secret
|
|
104
|
+
env:
|
|
105
|
+
IMAGE_SERVER_KNOWN_HOSTS: ${{ secrets.IMAGE_SERVER_KNOWN_HOSTS }}
|
|
106
|
+
run: |
|
|
107
|
+
set -euo pipefail
|
|
108
|
+
|
|
109
|
+
echo "Generating image_server_known_hosts..."
|
|
110
|
+
printf '%s\n' "$IMAGE_SERVER_KNOWN_HOSTS" > image_server_known_hosts
|
|
111
|
+
test -s image_server_known_hosts
|
|
112
|
+
chmod 644 image_server_known_hosts
|
|
113
|
+
|
|
89
114
|
- name: Upload rendered config.toml, firebase_config.json and docker-compose.yml
|
|
90
115
|
uses: appleboy/scp-action@v1
|
|
91
116
|
with:
|
|
@@ -93,7 +118,7 @@ jobs:
|
|
|
93
118
|
username: ${{ secrets.SERVER_USER }}
|
|
94
119
|
key: ${{ secrets.SERVER_SSH_KEY }}
|
|
95
120
|
port: ${{ secrets.PORT }}
|
|
96
|
-
source: "config.toml,firebase_config.json,docker-compose.yml"
|
|
121
|
+
source: "config.toml,firebase_config.json,docker-compose.yml,image_server_ssh_key,image_server_known_hosts"
|
|
97
122
|
target: "/home/${{ secrets.SERVER_USER }}/app_configs/core-framework"
|
|
98
123
|
|
|
99
124
|
- name: Deploy to Server via SSH
|
|
@@ -113,13 +138,30 @@ jobs:
|
|
|
113
138
|
DOCKERHUB_USERNAME="${{ inputs.dockerhub_username }}"
|
|
114
139
|
CONFIG_PATH="/home/${{ secrets.SERVER_USER }}/app_configs/core-framework/config.toml"
|
|
115
140
|
FIREBASE_CONFIG_PATH="/home/${{ secrets.SERVER_USER }}/app_configs/core-framework/firebase_config.json"
|
|
141
|
+
AVATAR_STAGING_PATH="/home/${{ secrets.SERVER_USER }}/app_configs/core-framework/avatar-staging"
|
|
142
|
+
IMAGE_SERVER_SSH_KEY_PATH="/home/${{ secrets.SERVER_USER }}/app_configs/core-framework/image_server_ssh_key"
|
|
143
|
+
IMAGE_SERVER_KNOWN_HOSTS_PATH="/home/${{ secrets.SERVER_USER }}/app_configs/core-framework/image_server_known_hosts"
|
|
116
144
|
|
|
117
145
|
echo "Pulling latest image"
|
|
118
146
|
DOCKERHUB_USERNAME="$DOCKERHUB_USERNAME" \
|
|
119
147
|
CONFIG_PATH="$CONFIG_PATH" \
|
|
120
148
|
FIREBASE_CONFIG_PATH="$FIREBASE_CONFIG_PATH" \
|
|
149
|
+
AVATAR_STAGING_PATH="$AVATAR_STAGING_PATH" \
|
|
150
|
+
IMAGE_SERVER_SSH_KEY_PATH="$IMAGE_SERVER_SSH_KEY_PATH" \
|
|
151
|
+
IMAGE_SERVER_KNOWN_HOSTS_PATH="$IMAGE_SERVER_KNOWN_HOSTS_PATH" \
|
|
121
152
|
docker compose -f "$COMPOSE_FILE" pull
|
|
122
153
|
|
|
154
|
+
echo "Preparing avatar staging directory and image server SSH key permissions"
|
|
155
|
+
mkdir -p "$AVATAR_STAGING_PATH"
|
|
156
|
+
docker run --rm \
|
|
157
|
+
--user root \
|
|
158
|
+
-v "$AVATAR_STAGING_PATH:/app/avatar-staging" \
|
|
159
|
+
-v "$IMAGE_SERVER_SSH_KEY_PATH:/app/image-server-ssh-key" \
|
|
160
|
+
-v "$IMAGE_SERVER_KNOWN_HOSTS_PATH:/app/image-server-known-hosts" \
|
|
161
|
+
--entrypoint "" \
|
|
162
|
+
"$DOCKERHUB_USERNAME/core-framework:latest" \
|
|
163
|
+
sh -c 'mkdir -p /app/avatar-staging && chown -R appuser:appgroup /app/avatar-staging && chown appuser:appgroup /app/image-server-ssh-key && chmod 600 /app/image-server-ssh-key && chown appuser:appgroup /app/image-server-known-hosts && chmod 644 /app/image-server-known-hosts'
|
|
164
|
+
|
|
123
165
|
echo "Running database migrations (cf-alembic iterates core_framework.bundled_alembic.ALEMBIC_DOMAINS in order)"
|
|
124
166
|
docker run --rm \
|
|
125
167
|
--network host \
|
|
@@ -131,6 +173,9 @@ jobs:
|
|
|
131
173
|
DOCKERHUB_USERNAME="$DOCKERHUB_USERNAME" \
|
|
132
174
|
CONFIG_PATH="$CONFIG_PATH" \
|
|
133
175
|
FIREBASE_CONFIG_PATH="$FIREBASE_CONFIG_PATH" \
|
|
176
|
+
AVATAR_STAGING_PATH="$AVATAR_STAGING_PATH" \
|
|
177
|
+
IMAGE_SERVER_SSH_KEY_PATH="$IMAGE_SERVER_SSH_KEY_PATH" \
|
|
178
|
+
IMAGE_SERVER_KNOWN_HOSTS_PATH="$IMAGE_SERVER_KNOWN_HOSTS_PATH" \
|
|
134
179
|
docker compose -f "$COMPOSE_FILE" up -d \
|
|
135
180
|
--remove-orphans \
|
|
136
181
|
--force-recreate \
|
|
@@ -10,6 +10,9 @@ wheels/
|
|
|
10
10
|
.venv
|
|
11
11
|
.env
|
|
12
12
|
|
|
13
|
+
# Avatar ingest staging (local disk)
|
|
14
|
+
avatar-staging/
|
|
15
|
+
|
|
13
16
|
# Logs
|
|
14
17
|
logs/
|
|
15
18
|
|
|
@@ -21,6 +24,11 @@ htmlcov/
|
|
|
21
24
|
# Firebase
|
|
22
25
|
firebase_config.json
|
|
23
26
|
|
|
27
|
+
# Image server SSH (local dev; production via deploy secrets)
|
|
28
|
+
image-server-known-hosts
|
|
29
|
+
image_server_known_hosts
|
|
30
|
+
image_server_ssh_key
|
|
31
|
+
|
|
24
32
|
# Cursor debug
|
|
25
33
|
firebase-debug.log
|
|
26
34
|
ui-debug.log
|
|
@@ -4,6 +4,68 @@ Notable changes to **core-framework** (import **`core_framework`**). Format foll
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.5.0] - 2026-05-26
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Avatar upload (v1):** **`POST /users/me/profile/avatar`**, **`media`** schema, worker variant processing, staging sweep cron, **`[avatar].staging_root`** config. See **`docs/flows/media/upload_pipeline.md`** and **`docs/flows/users/profile.md`**.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **Stale staging sweep:** **`delete_avatar_staging_blobs`** returns removed filenames; registry rows are deleted only when the staging file was removed or already missing (failed disk deletes retain the registry row for retry).
|
|
16
|
+
- **ARQ queue mismatch:** API **`RedisQueue`** pool now uses **`default_queue_name=core_framework:queue`**, matching worker consumers, so application enqueues (for example avatar variant processing) are no longer dropped on ARQ's default **`arq:queue`**.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **Breaking:** **`[image_server].known_hosts_path`** is required; **`SshAvatarFinalsPublisherAdapter`** verifies the image server SSH host key (no longer **`known_hosts=None`**). Deploy must provide **`IMAGE_SERVER_KNOWN_HOSTS`** and mount **`/app/image-server-known-hosts`** on the worker. See **`docs/deployment/image-server-ssh.md`**.
|
|
21
|
+
- **Breaking:** **`[avatar].avatar_host_domain`** is **host-only** in **`config.toml`** and deploy secret **`AVATAR_HOST_DOMAIN`** (for example **`avatar.example.com`**, no **`https://`**). The application prepends **`https://`** when assembling public avatar URLs. Values that still include a scheme are normalized at load time.
|
|
22
|
+
- **Breaking:** **`[banner]`** config aligns with **`[avatar]`**: **`banner_host_domain`** + **`default_banner_image`** (deploy secrets **`BANNER_HOST_DOMAIN`**, **`BANNER_DEFAULT_IMAGE`**) replace **`base_url`** / **`default_url`**. Hostname is host-only; public banner URLs derive **`https://`** in application code.
|
|
23
|
+
|
|
24
|
+
### Database
|
|
25
|
+
|
|
26
|
+
- **Media** baseline **`v1_media_init`**: **`media`** schema with **`avatar_staging_registry`** and **`avatar_ingest_lease`**. Hosts must set **`schema_media`** under **`[postgres_schemas]`**.
|
|
27
|
+
- **User** migration **`user_add_avatar_ingest_sequences`**: **`avatar_ingest_sequence`** and **`avatar_applied_sequence`** on **`user_profiles`**.
|
|
28
|
+
- Run **`uv run cf-alembic`** (or the host **`cf-alembic`** step) before deploying code that uses avatar upload or variant processing.
|
|
29
|
+
|
|
30
|
+
### Documentation
|
|
31
|
+
|
|
32
|
+
- **Avatar upload edge limits:** **`docs/deployment/edge-upload-limits.md`**, **`docs/deployment/Caddyfile.example`** — route-scoped reverse-proxy **`max_size`** (**6 MiB** recommended) for **`POST /users/me/profile/avatar`**; updated **`docs/flows/media/upload_pipeline.md`** and **`docs/flows/users/profile.md`**.
|
|
33
|
+
- **Image server SSH:** **`docs/deployment/image-server-ssh.md`**, **`docs/deployment/image-server-known-hosts.example`** — production checklist for **`known_hosts_path`** and **`IMAGE_SERVER_KNOWN_HOSTS`**.
|
|
34
|
+
- **`docs/flows/media/upload_pipeline.md`**: status and HTTP surface aligned with shipped v1 implementation.
|
|
35
|
+
- **`docs/flows/users/profile.md`**: **`POST /users/me/profile/avatar`** flow; **`PATCH`** no longer documents **`avatar_id`** as planned.
|
|
36
|
+
- **`docs/flows/users/avatar.md`**, **`docs/flows/users/change_history.md`**: avatar change path and ingest MIME notes updated.
|
|
37
|
+
- **`docs/flows/users/avatar.md`**, **`docs/flows/users/profile.md`**: **`avatar_host_domain`** documented as host-only; public URLs derive **`https://`** in application code.
|
|
38
|
+
- **`docs/domain-services-and-adapters.md`**: **`banner_host_domain`** / **`default_banner_image`** config shape documented alongside avatar.
|
|
39
|
+
|
|
40
|
+
## [1.4.0] - 2026-05-19
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- **Breaking:** **`avatar`** on profile and related user responses is an object (**`size_128`**, **`size_500`**, **`fallback`**) instead of a single URL string. See **`docs/flows/users/avatar.md`**.
|
|
45
|
+
- **Breaking:** **`[avatar]`** config uses **`avatar_host_domain`** + **`default_avatar_image`** (deploy env **`AVATAR_HOST_DOMAIN`**, **`AVATAR_DEFAULT_IMAGE`**) instead of **`base_url`** / **`default_url`**.
|
|
46
|
+
- **Typed PATCH payloads** — user (preferences, account, profile + admin profile), post, and comment PATCH routes map Pydantic requests to domain `*Update` types at the API boundary; no `model_dump(mode="json")` or dict partial-update payloads through application/domain/repository. See `docs/patch-update-typed-payload.md` and `docs/conventions.md` (*PATCH partial updates*).
|
|
47
|
+
- Self-service profile PATCH invalidates **`USER_DETAIL`** only when **`display_name`** or **`avatar_id`** change (fields present in cached identity). **`profile_visibility`** is coerced to **`ProfileVisibility`** on repository read.
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- Optional **`date_of_birth`** on user profiles: calendar date (`YYYY-MM-DD`), not in the future, omit unchanged / **`null`** clears. Exposed on **`GET`** / **`PATCH /users/me/profile`** and on admin user detail plus **`PATCH /admin/users/{user_id}/profile`**. Not returned on other users’ public surfaces (follow lists, post author cards, etc.).
|
|
52
|
+
|
|
53
|
+
### Database
|
|
54
|
+
|
|
55
|
+
- User domain migration **`user_add_date_of_birth`**: nullable **`date_of_birth`** column on **`user_profiles`**. Run **`uv run cf-alembic`** (or your host migration step) before deploying application code that reads or writes the field.
|
|
56
|
+
|
|
57
|
+
### Documentation
|
|
58
|
+
|
|
59
|
+
- **`docs/flows/users/avatar.md`**: multi-size **`avatar`** response shape and CDN URL convention.
|
|
60
|
+
- **`docs/flows/media/upload_pipeline.md`**: upload pipeline design and v1 implementation reference.
|
|
61
|
+
- **`docs/flows/users/profile.md`**: DOB on get/update flows and validation errors.
|
|
62
|
+
- **`docs/flows/users/change_history.md`**: profile **`date_of_birth`** changes are not written to change history (audit trigger unchanged).
|
|
63
|
+
- **`docs/patch-update-typed-payload.md`**: typed PATCH decisions and patterns for partial-update endpoints.
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
|
|
67
|
+
- **Dockerfile**: include **`README.md`** in the build context so hatchling can resolve the package readme.
|
|
68
|
+
|
|
7
69
|
## [1.3.0] - 2026-05-16
|
|
8
70
|
|
|
9
71
|
### Added
|
|
@@ -140,4 +202,6 @@ First **SemVer-stable** release per **`docs/package-api.md`**.
|
|
|
140
202
|
[1.1.1]: https://github.com/NepNepFFXIV/core-framework/compare/v1.1.0...v1.1.1
|
|
141
203
|
[1.2.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.1.1...v1.2.0
|
|
142
204
|
[1.3.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.2.0...v1.3.0
|
|
143
|
-
[
|
|
205
|
+
[1.4.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.3.0...v1.4.0
|
|
206
|
+
[1.5.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.4.0...v1.5.0
|
|
207
|
+
[unreleased]: https://github.com/NepNepFFXIV/core-framework/compare/v1.5.0...HEAD
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: core-framework
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
4
4
|
Summary: Core framework package (import as core_framework)
|
|
5
5
|
Project-URL: Homepage, https://github.com/NepNepFFXIV/core-framework
|
|
6
6
|
Project-URL: Repository, https://github.com/NepNepFFXIV/core-framework
|
|
7
7
|
License-Expression: MIT
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
Requires-Python: >=3.14
|
|
10
|
+
Requires-Dist: aiofiles>=25.1.0
|
|
10
11
|
Requires-Dist: alembic>=1.18.4
|
|
11
12
|
Requires-Dist: arq>=0.28.0
|
|
12
13
|
Requires-Dist: async-lru>=2.1.0
|
|
13
14
|
Requires-Dist: asyncer>=0.0.14
|
|
14
15
|
Requires-Dist: asyncpg>=0.31.0
|
|
16
|
+
Requires-Dist: asyncssh>=2.23.0
|
|
15
17
|
Requires-Dist: brotli-asgi>=1.6.0
|
|
16
18
|
Requires-Dist: cashews[redis]>=7.5.0
|
|
17
19
|
Requires-Dist: device-detector>=6.2.0
|
|
@@ -22,6 +24,7 @@ Requires-Dist: itsdangerous>=2.2.0
|
|
|
22
24
|
Requires-Dist: logfire[asyncpg,fastapi,httpx,redis]>=4.32.1
|
|
23
25
|
Requires-Dist: mashumaro[orjson]>=3.20
|
|
24
26
|
Requires-Dist: orjson>=3.11.7
|
|
27
|
+
Requires-Dist: pillow>=12.2.0
|
|
25
28
|
Requires-Dist: python-ulid>=3.1.0
|
|
26
29
|
Requires-Dist: structlog>=25.5.0
|
|
27
30
|
Requires-Dist: tenacity>=9.1.4
|