Skip to content

[ENG-2185]#7133

Merged
JadeCara merged 33 commits intomainfrom
ENG-2185-add-new-jsob-tree-col
Jan 12, 2026
Merged

[ENG-2185]#7133
JadeCara merged 33 commits intomainfrom
ENG-2185-add-new-jsob-tree-col

Conversation

@JadeCara
Copy link
Copy Markdown
Contributor

@JadeCara JadeCara commented Dec 16, 2025

Ticket ENG-2185

Description Of Changes

🎯 Create manual tasks on the consent graph.

There are going to be a few PRs cleaning up some un-used functionality or adjusting functionality for improved usage patterns. This PR adds a condition tree which will hold the full conditional dependency tree in a jsonb column on the "root condition." It also migrates current conditions to store their full tree on their root node. This will allow us to access the full tree without building it or having to build out all of the conditions each time. It is a clean up based on the first few rounds of customer use and learning actual usage patterns.

This PR:

  • Update the Conditional Dependency table with a JSONB dictionary column. This will allow us to thoroughly test the migration without being destructive.

Future PRs:

  • Remove the extra columns and rows for conditional dependencies once the tree has been implemented and tested.
  • The logging being used by manual tasks and front end data is entirely the general audit logs so the manual task log table and utilities are now redundant. ENG-2185 #7124
  • Allow conditional dependencies to be assigned to a specific policy type or id
  • Allow users to be assigned to specific submissions rather than the whole task.

Code Changes

  • .fides/db_dataset.yml - add new column
  • src/fides/api/alembic/migrations/versions/ adds new colun
  • src/fides/api/db/base.py - adds new column and updates the existing functionality to use the new tree columns. Removes some functionality around rebuilding the tree details.
  • src/fides/api/models/digest/conditional_dependencies.py - updates some doc strings/comments and some functionality around returning the tree
  • src/fides/api/models/digest/conditional_dependencies.py - updates some docstrings and comments, updates
  • src/fides/api/models/manual_task/manual_task.py - Updates some docstrings and removes some un-needed code.
  • Updated all tests adding the condition tree

Steps to Confirm

There should be NO change in functionality.

If you have some manual tasks/conditions and/or digests hanging around feel free to skip to step 5)

  1. Starting with main (fides and fidesplus)
  2. Create a manual task and add some conditions to integrations
Screenshot 2026-01-05 at 3 27 25 PM Screenshot 2026-01-05 at 3 28 05 PM

In the DB you can view the conditional dependencies (for the ones in the example it looks like this)

SELECT * FROM manual_task_conditional_dependency;
-[ RECORD 1 ]----+------------------------------------------------------
created_at       | 2026-01-05 15:27:56.024225+00
updated_at       | 2026-01-05 15:27:56.024225+00
id               | man_ce9b0e4c-1d37-4800-a683-e179d89f8080
manual_task_id   | man_9e921296-e8f7-46de-8bfc-f53cd681ad63
parent_id        | 
condition_type   | group
field_address    | 
operator         | 
value            | 
logical_operator | and
sort_order       | 0
-[ RECORD 2 ]----+------------------------------------------------------
created_at       | 2026-01-05 15:27:56.037828+00
updated_at       | 2026-01-05 15:27:56.037828+00
id               | man_4d75dc75-49c3-425c-bd50-897e9efe33bc
manual_task_id   | man_9e921296-e8f7-46de-8bfc-f53cd681ad63
parent_id        | man_ce9b0e4c-1d37-4800-a683-e179d89f8080
condition_type   | leaf
field_address    | privacy_request.location_country
operator         | eq
value            | "PT"
logical_operator | 
sort_order       | 1
-[ RECORD 3 ]----+------------------------------------------------------
created_at       | 2026-01-05 15:27:56.040779+00
updated_at       | 2026-01-05 15:27:56.040779+00
id               | man_ae2f626d-7da7-498f-a5af-79295b4e4f9a
manual_task_id   | man_9e921296-e8f7-46de-8bfc-f53cd681ad63
parent_id        | man_ce9b0e4c-1d37-4800-a683-e179d89f8080
condition_type   | leaf
field_address    | bigquery_example_test_dataset:customer_039e6fc2:email
operator         | exists
value            | null
logical_operator | 
sort_order       | 2
  1. Create a digest
Screenshot 2026-01-05 at 3 30 20 PM

Digests auto create their own conditions for now. It should look something like this in the db

SELECT * FROM digest_condition;
-[ RECORD 1 ]---------+----------------------------------------------
created_at            | 2026-01-05 15:30:12.706011+00
updated_at            | 2026-01-05 15:30:12.706011+00
id                    | dig_7aeff1f7-b25c-4cbf-94d4-36c24d25447b
digest_config_id      | dig_54177a40-2a99-4411-b22c-91232df1b99d
parent_id             | 
digest_condition_type | RECEIVER
condition_type        | group
field_address         | 
operator              | 
value                 | 
logical_operator      | and
sort_order            | 0
-[ RECORD 2 ]---------+----------------------------------------------
created_at            | 2026-01-05 15:30:12.735612+00
updated_at            | 2026-01-05 15:30:12.735612+00
id                    | dig_4c890484-4079-437e-aeaa-1aa077d4b5b8
digest_config_id      | dig_54177a40-2a99-4411-b22c-91232df1b99d
parent_id             | dig_7aeff1f7-b25c-4cbf-94d4-36c24d25447b
digest_condition_type | RECEIVER
condition_type        | leaf
field_address         | fidesuser.manual_task_references.reference_id
operator              | exists
value                 | null
logical_operator      | 
sort_order            | 1

...

-[ RECORD 7 ]---------+----------------------------------------------
created_at            | 2026-01-05 15:30:12.771332+00
updated_at            | 2026-01-05 15:30:12.771332+00
id                    | dig_e608be6a-c6c8-417d-8592-a6c7618a2791
digest_config_id      | dig_54177a40-2a99-4411-b22c-91232df1b99d
parent_id             | dig_cbf882fb-de8f-46d1-a07e-5471c2c2c10d
digest_condition_type | CONTENT
condition_type        | leaf
field_address         | privacyrequest.status
operator              | eq
value                 | "requires_input"
logical_operator      | 
sort_order            | 2
  1. Create a couple privacy requests (that meet the conditions) - dont submit the tasks yet.
  2. Next build fidesplus FidesPlus 2932 pointed at this branch.
  3. check the manual task and digest conditional dependencies to verify that the tree now exists and has the correct conditions.

Example the root condition should now look like this:

SELECT * FROM manual_task_conditional_dependency;

...

created_at       | 2026-01-05 15:27:56.024225+00
updated_at       | 2026-01-05 15:27:56.024225+00
id               | man_ce9b0e4c-1d37-4800-a683-e179d89f8080
manual_task_id   | man_9e921296-e8f7-46de-8bfc-f53cd681ad63
parent_id        | 
condition_type   | group
field_address    | 
operator         | 
value            | 
logical_operator | and
sort_order       | 0
condition_tree   | {"conditions": [{"value": "PT", "operator": "eq", "field_address": "privacy_request.location_country"}, {"value": null, "operator": "
exists", "field_address": "bigquery_example_test_dataset:customer_039e6fc2:email"}], "logical_operator": "and"}
SELECT * FROM digest_condition;

...

-[ RECORD 6 ]---------+------------------------------------------------------------------------------------------------

created_at            | 2026-01-05 15:30:12.763974+00
updated_at            | 2026-01-05 15:30:12.763974+00
id                    | dig_cbf882fb-de8f-46d1-a07e-5471c2c2c10d
digest_config_id      | dig_54177a40-2a99-4411-b22c-91232df1b99d
parent_id             | 
digest_condition_type | CONTENT
condition_type        | group
field_address         | 
operator              | 
value                 | 
logical_operator      | and
sort_order            | 0
condition_tree        | {"conditions": [{"value": "completed", "operator": "neq", "field_address": "manual_task_instance.status"}, {"value": "requires_i
nput", "operator": "eq", "field_address": "privacyrequest.status"}], "logical_operator": "and"}
-[ RECORD 7 ]---------+-------------------------------------------------------------------------------------------------

created_at            | 2026-01-05 15:30:12.706011+00
updated_at            | 2026-01-05 15:30:12.706011+00
id                    | dig_7aeff1f7-b25c-4cbf-94d4-36c24d25447b
digest_config_id      | dig_54177a40-2a99-4411-b22c-91232df1b99d
parent_id             | 
digest_condition_type | RECEIVER
condition_type        | group
field_address         | 
operator              | 
value                 | 
logical_operator      | and
sort_order            | 0
condition_tree        | {"conditions": [{"value": null, "operator": "exists", "field_address": "fidesuser.manual_task_references.reference_id"}, {"value
": false, "operator": "eq", "field_address": "fidesuser.disabled"}, {"value": null, "operator": "exists", "field_address": "fidesuser.email_address"}], 
"logical_operator": "and"}
  1. Manual tasks, conditional dependencies etc should continue to function as before.
  2. Create a ManualTask with several submissions required and several conditions, both dataset and privacy request based. - Note any new conditions created should only create the new tree and 1 row.
  3. Create privacy requests that meet and do not meet the requirements. Verify that they create/do not create as expected.
  4. Verify that you can complete the manual tasks with no errors. Including the ones from step 4)
  5. Verify that you can see the skipped logs and the manual task and manual task actions appear on the DSR activity log.
  6. Verify that you receive the access package with all expected manual task inclusions (attachments and text)
  7. Delete manual task integration

Pre-Merge Checklist

  • Issue requirements met
  • All CI pipelines succeeded
  • CHANGELOG.md updated
    • Add a db-migration This indicates that a change includes a database migration label to the entry if your change includes a DB migration
    • Add a high-risk This issue suggests changes that have a high-probability of breaking existing code label to the entry if your change includes a high-risk change (i.e. potential for performance impact or unexpected regression) that should be flagged
    • Updates unreleased work already in Changelog, no new entry necessary
  • UX feedback:
    • All UX related changes have been reviewed by a designer
    • No UX review needed
  • Followup issues:
    • Followup issues created
    • No followup issues
  • Database migrations:
    • Ensure that your downrev is up to date with the latest revision on main
    • Ensure that your downgrade() migration is correct and works
      • If a downgrade migration is not possible for this change, please call this out in the PR description!
    • No migrations
  • Documentation:
    • Documentation complete, PR opened in fidesdocs
    • Documentation issue created in fidesdocs
    • If there are any new client scopes created as part of the pull request, remember to update public-facing documentation that references our scope registry
    • No documentation updates required

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Dec 16, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Review Updated (UTC)
fides-plus-nightly Ignored Ignored Preview Jan 12, 2026 6:28pm
fides-privacy-center Ignored Ignored Jan 12, 2026 6:28pm

@JadeCara JadeCara mentioned this pull request Dec 16, 2025
18 tasks
@codecov
Copy link
Copy Markdown

codecov bot commented Dec 16, 2025

Codecov Report

❌ Patch coverage is 96.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.24%. Comparing base (0bc95f8) to head (ba6556f).

Files with missing lines Patch % Lines
src/fides/api/task/manual/manual_task_utils.py 88.88% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7133      +/-   ##
==========================================
- Coverage   87.24%   87.24%   -0.01%     
==========================================
  Files         535      535              
  Lines       35335    35324      -11     
  Branches     4114     4117       +3     
==========================================
- Hits        30828    30817      -11     
+ Misses       3617     3615       -2     
- Partials      890      892       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@JadeCara JadeCara marked this pull request as ready for review December 29, 2025 18:10
@JadeCara JadeCara requested a review from a team as a code owner December 29, 2025 18:10
@JadeCara JadeCara removed the request for review from a team December 29, 2025 18:10
Comment on lines +21 to +23
# TypeAdapter for deserializing JSONB to Condition (handles Union discrimination)
ConditionTypeAdapter: TypeAdapter[Condition] = TypeAdapter(Condition)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow here -- probably cause I don't know enough pydantic heh -- what union are we discriminating?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A Condition is a single Pydantic type that can be a group or a leaf. So the Type Adapter works for deserializing groups correctly and leafs correctly :)

Comment on lines +94 to +96
# JSONB storage for full condition tree
condition_tree = Column(JSONB, nullable=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious about the reasoning behind storing the full condition tree in the condition -- is it performance gains , so we avoid calculating the condition tree at runtime?

also I'm realizing I'm not entirely sure I understand what we mean by condition tree 😅 I was expecting this model to have some sort of relationship to itself/other conditions but don't see that?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah - the original design was that each condition was stored in its own line. To get the full condition tree you needed to know what conditions were grouped together and rebuild the tree.

The actual usage pattern turns out to be more - remove all, create a new tree than update a single line. Its not a huge time spent doing it, but there is more risks for bugs when we have to rebuild the tree when we want to use it than just keeping it built.

The actual trees are validated using the pydantic models. This will remove a lot of moving pieces. The next PR in the series removes the un-used columns (after doing good tests/verification in nightly etc) that all works as expected.

Comment on lines +208 to +209
def get_root_condition(cls, db: Session, **kwargs: Any) -> Optional[Condition]:
"""Get the root condition tree for a parent entity.
"""Get the condition tree for a parent entity.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be renamed to get_condition_tree or similar ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! Updating!

Comment on lines -47 to +51
- Multi-type hierarchy means one digest_config can have multiple independent
condition trees, each with a different digest_condition_type (RECEIVER, CONTENT, PRIORITY)
- Within each tree, all nodes must have the same digest_condition_type
- This enables separate condition logic for different aspects of digest processing
Each digest_config can have up to three independent condition trees,
one per digest_condition_type (RECEIVER, CONTENT, PRIORITY).
This enables separate condition logic for different aspects of digest processing.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to keep the "within each tree, all nodes must have the same digest_condition_type ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! good catch thank you :)

id = Column(String(255), primary_key=True, default=FidesBase.generate_uuid)

# Foreign key relationships
# Foreign key to parent digest config
Copy link
Copy Markdown
Contributor

@erosselli erosselli Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah I see , the relationship I was asking about is in the specific model and not the base one. Okay so each condition contains the tree starting from itself and down? e.g if my tree is

A
|_B
   |_C
   |_D
|_E 

A will contain the entire tree, B will contain a tree with B as the root and C and D as the children , E will contain a tree with just itself, and so will C and D?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now we are just putting the whole thing in A.
The next PR removes all relationship cols and all orphaned dependents. The whole tree will just be in A.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh okay I see!

Comment on lines 107 to +108
__table_args__ = (
# TODO update to this in next migration to use UniqueConstraint
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this todo intentional?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes :) It gets resolved in the next PR. I can take it out here though since that PR is almost ready for review as well :)

privacy_request_field_addresses: set[str] = set(
address
for address in all_field_addresses
if address.startswith("privacy_request.")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the raw string here makes me a bit nervous, do we have other places that use it? can we move it to a constant ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we also use it below :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point and we do already have it as a constant! Let me update that!

Copy link
Copy Markdown
Contributor

@erosselli erosselli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tested manually but the code looks good, thanks for making the changes. I think codecov is reporting some uncovered lines (and the changelog is missing :) still the old-school entry since I haven't merged my changelog PR yet)

@JadeCara
Copy link
Copy Markdown
Contributor Author

@greptile please review

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@JadeCara
Copy link
Copy Markdown
Contributor Author

@greptile please review

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@JadeCara
Copy link
Copy Markdown
Contributor Author

@greptile please review

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@JadeCara JadeCara added this pull request to the merge queue Jan 12, 2026
Merged via the queue into main with commit 6576184 Jan 12, 2026
51 of 52 checks passed
@JadeCara JadeCara deleted the ENG-2185-add-new-jsob-tree-col branch January 12, 2026 20:18
JadeCara added a commit that referenced this pull request Jan 13, 2026
Co-authored-by: Jade Wibbels <jade@ethyca.com>
jack-gale-ethyca pushed a commit that referenced this pull request Jan 26, 2026
Co-authored-by: Jade Wibbels <jade@ethyca.com>
mfbrown pushed a commit that referenced this pull request Jan 27, 2026
Co-authored-by: Jade Wibbels <jade@ethyca.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants