ENG-3517: Foundation - attachment_user_provided model + repository#8110
Open
mikeGarifullin wants to merge 1 commit intomainfrom
Open
ENG-3517: Foundation - attachment_user_provided model + repository#8110mikeGarifullin wants to merge 1 commit intomainfrom
mikeGarifullin wants to merge 1 commit intomainfrom
Conversation
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
2db5bf4 to
bd91f16
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8110 +/- ##
==========================================
+ Coverage 85.23% 85.26% +0.03%
==========================================
Files 638 641 +3
Lines 42011 42091 +80
Branches 4937 4941 +4
==========================================
+ Hits 35807 35888 +81
Misses 5096 5096
+ Partials 1108 1107 -1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
18 tasks
mikeGarifullin
added a commit
that referenced
this pull request
May 6, 2026
Schema + storage + service-extension half of the upload foundation. Pairs with the data-layer PR (#8110); the upload service and endpoint live in fidesplus and consume both. Adds the Privacy Center config variant for file fields, a magic-byte sniff catalog so the upload route can verify a payload's claimed type at byte zero (client-supplied Content-Type is not trusted), two config knobs for the global upload ceilings, and two PrivacyRequestService hook points so fidesplus can plug attachment-resolve and attachment-promote into the existing submission flow without overriding create_privacy_request wholesale. * schemas/privacy_center_config.py: FileUploadCustomPrivacyRequestField discriminated-union variant (max_size_bytes > 0, allowed_file_types non-empty + must be a subset of AllowedFileType). * schemas/attachment.py: PrivacyRequestAttachment response payload. * service/storage/util.py: FilesMagicBytes (per-extension signatures + candidates / extensions_without_magic), AllowedFileType helpers, MIME_TO_EXTENSION, extension_for_mime, FileUploadConstraints (self-validated bag). * service/privacy_request/privacy_request_service.py: - _resolve_attachment_state(req, action) and _promote_attachment_state(privacy_request, state) — overridable no-op hooks. - create_privacy_request resolves the action once, forwards to validators + the resolve hook, then promotes after request creation. Promotion failures delete the just-created row and rewrap with a generic message (sensitive detail goes to logs / __cause__, not the user-facing error). * common/urn_registry.py: route constant for the future fidesplus upload endpoint. * config/security_settings.py: request_attachment_max_bytes ceiling. * config/execution_settings.py: attachment_orphan_ttl_seconds. * Tests: schema variant, magic-byte catalog, FileUploadConstraints, attachment-state hook defaults, create_privacy_request orchestration (hook receives the same action sentinel), promotion-failure rollback (request deleted, leaky detail sanitized, delete-failure still surfaces promotion exception, hook-raised PrivacyRequestError isn't double-rewrapped).
mikeGarifullin
added a commit
that referenced
this pull request
May 6, 2026
Schema + storage + service-extension half of the upload foundation. Pairs with the data-layer PR (#8110); the upload service and endpoint live in fidesplus and consume both. Adds the Privacy Center config variant for file fields, a magic-byte sniff catalog so the upload route can verify a payload's claimed type at byte zero (client-supplied Content-Type is not trusted), two config knobs for the global upload ceilings, and two PrivacyRequestService hook points so fidesplus can plug attachment-resolve and attachment-promote into the existing submission flow without overriding create_privacy_request wholesale. * schemas/privacy_center_config.py: FileUploadCustomPrivacyRequestField discriminated-union variant (max_size_bytes > 0, allowed_file_types non-empty + must be a subset of AllowedFileType). * schemas/attachment.py: PrivacyRequestAttachment response payload. * service/storage/util.py: FilesMagicBytes (per-extension signatures + candidates / extensions_without_magic), AllowedFileType helpers, MIME_TO_EXTENSION, extension_for_mime, FileUploadConstraints (self-validated bag). * service/privacy_request/privacy_request_service.py: - _resolve_attachment_state(req, action) and _promote_attachment_state(privacy_request, state) — overridable no-op hooks. - create_privacy_request resolves the action once, forwards to validators + the resolve hook, then promotes after request creation. Promotion failures delete the just-created row and rewrap with a generic message (sensitive detail goes to logs / __cause__, not the user-facing error). * common/urn_registry.py: route constant for the future fidesplus upload endpoint. * config/security_settings.py: request_attachment_max_bytes ceiling. * config/execution_settings.py: attachment_orphan_ttl_seconds. * Tests: schema variant, magic-byte catalog, FileUploadConstraints, attachment-state hook defaults, create_privacy_request orchestration (hook receives the same action sentinel), promotion-failure rollback (request deleted, leaky detail sanitized, delete-failure still surfaces promotion exception, hook-raised PrivacyRequestError isn't double-rewrapped).
mikeGarifullin
added a commit
that referenced
this pull request
May 6, 2026
Schema + storage + service-extension half of the upload foundation. Pairs with the data-layer PR (#8110); the upload service and endpoint live in fidesplus and consume both. Adds the Privacy Center config variant for file fields, a magic-byte sniff catalog so the upload route can verify a payload's claimed type at byte zero (client-supplied Content-Type is not trusted), two config knobs for the global upload ceilings, and two PrivacyRequestService hook points so fidesplus can plug attachment-resolve and attachment-promote into the existing submission flow without overriding create_privacy_request wholesale. * schemas/privacy_center_config.py: FileUploadCustomPrivacyRequestField discriminated-union variant (max_size_bytes > 0, allowed_file_types non-empty + must be a subset of AllowedFileType). * schemas/attachment.py: PrivacyRequestAttachment response payload. * service/storage/util.py: FilesMagicBytes (per-extension signatures + candidates / extensions_without_magic), AllowedFileType helpers, MIME_TO_EXTENSION, extension_for_mime, FileUploadConstraints (self-validated bag). * service/privacy_request/privacy_request_service.py: - _resolve_attachment_state(req, action) and _promote_attachment_state(privacy_request, state) — overridable no-op hooks. - create_privacy_request resolves the action once, forwards to validators + the resolve hook, then promotes after request creation. Promotion failures delete the just-created row and rewrap with a generic message (sensitive detail goes to logs / __cause__, not the user-facing error). * common/urn_registry.py: route constant for the future fidesplus upload endpoint. * config/security_settings.py: request_attachment_max_bytes ceiling. * config/execution_settings.py: attachment_orphan_ttl_seconds. * Tests: schema variant, magic-byte catalog, FileUploadConstraints, attachment-state hook defaults, create_privacy_request orchestration (hook receives the same action sentinel), promotion-failure rollback (request deleted, leaky detail sanitized, delete-failure still surfaces promotion exception, hook-raised PrivacyRequestError isn't double-rewrapped).
dc2b693 to
5959e6c
Compare
Data layer for the data-subject file-upload feature: `attachment_user_provided`
table + repository primitives. Upload service + endpoint live in fidesplus
and consume this PR.
Lifecycle: uploaded → promoted | deleted (deleted rows kept for audit).
The (field_name, property_id, policy_key) triple binds an upload to the
privacy-center config it was issued under so re-validation at submission
can refuse mismatches. storage_key is intentionally non-FK so audit rows
survive storage-config churn.
* Model: AttachmentUserProvidedStatus enum + AttachmentUserProvided model
with composite (status, created_at) index for the orphan-sweep predicate.
field_name/property_id/policy_key are intentionally non-FK so audit rows
survive policy/property/field rename or delete between upload and
submission.
* Migration 9b449105864d, reversible.
* Repository:
- create_uploaded: insert + flush + refresh, returns AttachmentUserProvidedRecord
- lock_by_ids: SELECT … FOR UPDATE ordered by id (deadlock-safe under
overlapping batches), returns dict[str, row] keyed by id; missing ids
absent. session is required (no @with_optional_sync_session) since the
lock is only meaningful inside a caller-owned transaction.
- assert_all_uploaded: lifecycle precondition check
- mark_promoted: pure mutation, caller owns the session boundary
- mark_deleted: in-session only, no commit
- list_uploaded_older_than: orphan-sweep query, exclusive cutoff
* Domain entities (AttachmentUserProvidedRecord) + exceptions
(AttachmentsServiceError + InvalidAttachmentStateError).
* Test suite covers every public method incl. lifecycle invariants,
empty-list short-circuit, missing-id detection, cutoff exclusivity.
* .fides/db_dataset.yml: dataset entries for the new table.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ticket ENG-3517
Description Of Changes
Data layer for the data-subject file-upload feature:
attachment_user_providedtable + repository primitives. The upload service + endpoint live in fidesplus and consume this PR.The table tracks an upload's lifecycle independently of
attachmentso a user can upload before a privacy request exists. Lifecycle:uploaded → promoted | deleted.deletedrows are kept for audit. The(field_name, property_id, policy_key)triple binds an upload to the privacy-center config it was issued under so re-validation at submission can refuse mismatches.storage_keyis a plain string (no FK) so rows survive storage-config churn.Code Changes
models/attachment.py:AttachmentUserProvidedStatusenum +AttachmentUserProvidedmodel.(field_name, property_id, policy_key)are required at insert. Composite index(status, created_at)for the orphan-sweep predicate.alembic/.../9b449105864d_add_attachment_user_provided.py: table + enum + index.down_revision = "3a91e5d4f7b2". Reversible.service/privacy_request_attachments/privacy_request_attachments_repository.py:create_uploaded— insert + flush + refresh, returnsAttachmentUserProvidedRecordlock_by_ids—SELECT … FOR UPDATEordered byid(deadlock-safe under overlapping batches), returnsdict[str, row]so callers can detect missing idsassert_all_uploaded— fail-fast preconditionmark_promoted— pure mutation, caller owns the sessionmark_deleted— in-session mutation, no commitlist_uploaded_older_than— orphan-sweep, exclusive cutoffservice/privacy_request_attachments/privacy_request_attachments_entities.py:AttachmentUserProvidedRecorddetach-safe dataclass.service/privacy_request_attachments/privacy_request_attachments_exceptions.py:AttachmentsServiceErrorbase +InvalidAttachmentStateError. Other domain errors will land with their consumers.tests/ops/service/privacy_request_attachments/test_repository.py: lifecycle suite covering every public repo method..fides/db_dataset.yml: dataset entries for the new table.Steps to Confirm
alembic upgrade head— revision9b449105864dapplies.\d attachment_user_providedin psql — columns,attachmentuserprovidedstatusenum,ix_attachment_user_provided_status_created_atindex all present.alembic downgrade -1— table, enum, and index drop cleanly.alembic upgrade headagain to land on the new revision.nox -s "pytest(ops-unit-non-api)" -- tests/ops/service/privacy_request_attachments/test_repository.py— all green.Pre-Merge Checklist
CHANGELOG.mdupdatedmaindowngrade()migration is correct and works