Skip to content

IFC-2048: Remove optional and unique attributes from profile schema#8577

Open
solababs wants to merge 18 commits intodevelopfrom
sb-11032025-remove-optional-and-unique-attributes-from-profile-schema-ifc-2048
Open

IFC-2048: Remove optional and unique attributes from profile schema#8577
solababs wants to merge 18 commits intodevelopfrom
sb-11032025-remove-optional-and-unique-attributes-from-profile-schema-ifc-2048

Conversation

@solababs
Copy link
Copy Markdown
Contributor

@solababs solababs commented Mar 11, 2026

Why

Optional attributes that participate in uniqueness constraints must not appear in profile schemas. When a profile sets such an attribute, every node using that profile would share the same value, violating the uniqueness constraint at scale.

Closes #7644 and #7646

What changed

  • Optional attributes that are part of a uniqueness_constraints entry are now excluded when generating a profile from a node schema.
  • Added SchemaBranch.check_attr_in_uniqueness_constraint to check whether an attribute name appears in any uniqueness constraint path (handles both bare attribute names and attr__field paths).
  • generate_profile_from_node now skips an attribute if it is both optional and in a uniqueness constraint.
  • Required attributes (non-optional) in uniqueness constraints are still included in profiles — no change to that behavior.
  • No schema migrations, no API contract changes.

Suggested review order

  1. SchemaBranch.check_attr_in_uniqueness_constraint — the new helper
  2. The updated if condition in generate_profile_from_node
  3. test_profile_does_not_contain_optional_unique_attributes — the regression test

How to review

Focus on the condition logic in generate_profile_from_node: an attribute is skipped when optional=True and it appears in a uniqueness constraint. Non-optional attributes in constraints are intentionally unaffected.

The constraint path matching handles both attr__value style paths and bare attr names via startswith(f"{attr}__") or constraint_path == attr.

How to test

uv run pytest backend/tests/component/core/schema_manager/test_manager_schema.py::test_profile_does_not_contain_optional_unique_attributes -v

Expected: the name attribute is absent from ProfileNetworkRouter when it is optional and listed in uniqueness_constraints.

Impact & rollout

  • Backward compatibility: Profiles previously generated from schemas with optional unique attributes would have included those attributes. After this change they will not. Existing stored profiles are unaffected; only schema generation changes.
  • Performance: No impact.
  • Config/env changes: None.
  • Deployment notes: Safe to deploy independently.

Checklist

  • Tests added/updated
  • Changelog entry added (uv run towncrier create ...)
  • External docs updated (if user-facing or ops-facing change)
  • Internal .md docs updated (internal knowledge and AI code tools knowledge)

Summary by CodeRabbit

  • New Features

    • Added a migration to adjust schemas when optional attributes are added to or removed from uniqueness constraints, keeping generated profile schemas consistent.
  • Bug Fixes

    • Profile generation now omits optional attributes that are governed by uniqueness constraints, preventing unintended exposure of unique optional fields.
  • Documentation

    • Schema reference updated to mark uniqueness-constraint changes as requiring migrations.

@solababs solababs requested a review from a team as a code owner March 11, 2026 17:00
@github-actions github-actions bot added the group/backend Issue related to the backend (API Server, Git Agent) label Mar 11, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The change prevents generated Profile schemas from including attributes that are optional and part of a node's uniqueness constraints. It adds two helper methods on BaseNodeSchema to detect uniqueness-constraint membership and to decide if an attribute should support profiles. Profile-generation checks were updated to use that helper across schema generation and migration queries. A new migration (node.uniqueness_constraints.update) and accompanying tests were added to handle uniqueness-constraint membership changes; migration registry, docs, and OpenAPI metadata for uniqueness_constraints update behavior were updated accordingly.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes include broader scope beyond #7644: schema migration support infrastructure (node_uniqueness_constraints_update.py, migration registry, UpdateSupport.MIGRATION_REQUIRED) addresses handling when uniqueness constraints change post-deployment, which exceeds the issue's stated scope. Clarify whether the migration infrastructure changes (UpdateSupport.MIGRATION_REQUIRED, NodeUniquenessConstraintsUpdateMigration) are in scope for this PR or should be deferred pending design discussion per reviewer feedback.
Docstring Coverage ⚠️ Warning Docstring coverage is 42.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly and specifically describes the primary change: preventing optional unique attributes from appearing in profile schemas, directly addressing issue #7644.
Description check ✅ Passed Description covers all major sections: problem statement, behavioral changes, implementation notes, test instructions, impact/rollout, and checklist. Follows template structure appropriately.
Linked Issues check ✅ Passed Code changes directly address issue #7644: optional attributes in uniqueness constraints are now excluded from generated profile schemas via the check_if_attr_supports_profiles helper and updated condition logic.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Mar 11, 2026

Merging this PR will not alter performance

✅ 12 untouched benchmarks


Comparing sb-11032025-remove-optional-and-unique-attributes-from-profile-schema-ifc-2048 (a4a87a4) with develop (508e42b)

Open in CodSpeed

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
backend/infrahub/core/schema/schema_branch.py (1)

2369-2376: Document the new helper's matching rules.

Please add a docstring here. The helper now defines non-trivial behavior (attr and attr__field matching), and backend Python changes are supposed to document public methods consistently.

As per coding guidelines, "Always use triple quotes (""") for Python docstrings" and "Follow Google-style docstring format in Python".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/infrahub/core/schema/schema_branch.py` around lines 2369 - 2376, Add
a Google-style triple-quoted docstring to the function
check_attr_in_uniqueness_constraint describing its purpose and matching rules:
it checks whether a given attribute name (attr) appears in any
NodeSchema.uniqueness_constraints either exactly or as a prefix for nested
fields using the "attr__field" pattern (i.e., constraint_path == attr or
constraint_path.startswith(f"{attr}__")). Document the parameters (attr: str,
node_schema: NodeSchema), the boolean return value, and a brief example or note
about the expected uniqueness_constraints structure to clarify behavior.
changelog/7644.added.md (1)

1-1: Clarify changelog entry wording.

The current wording could be more precise about when attributes are excluded. Based on the PR objectives, attributes are excluded when they are both optional AND participate in uniqueness constraints (not just when they have unique=True).

✍️ Suggested rewording for clarity
-Added condition to restrict adding attributes that are unique and optional to generated profile schemas
+Restrict optional attributes that participate in uniqueness constraints from being added to generated profile schemas

or alternatively:

-Added condition to restrict adding attributes that are unique and optional to generated profile schemas
+Profile schemas now exclude attributes that are both optional and part of uniqueness constraints

This makes it clearer that the restriction applies specifically to attributes in uniqueness_constraints, which aligns with the implementation and test case.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@changelog/7644.added.md` at line 1, Update the changelog sentence to
explicitly state that attributes are excluded only when they are both optional
and part of uniqueness constraints (i.e., participate in
uniqueness_constraints), not merely when they have unique=True; mention
"optional and participate in uniqueness_constraints" and optionally reference
the `uniqueness_constraints` term and `unique=True` to clarify the distinction.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/infrahub/core/schema/schema_branch.py`:
- Around line 2369-2376: The helper check_attr_in_uniqueness_constraint
currently only inspects node_schema.uniqueness_constraints, so optional
attributes with unique=True can slip through before constraints are
materialized; update check_attr_in_uniqueness_constraint to also return True if
the node_schema defines the attribute with unique=True (e.g., inspect
node_schema.attributes[attr].unique or equivalent attribute metadata) so profile
generation/pre-validation will treat it as constrained; apply the same change at
the parallel location referenced around the lines handling the same logic (the
duplicate block near 2429-2431) so both checks consider attribute-level
unique=True in addition to node_schema.uniqueness_constraints.

In `@backend/tests/component/core/schema_manager/test_manager_schema.py`:
- Around line 4667-4682: Add a Google-style docstring to the test function
test_profile_does_not_contain_optional_unique_attributes describing its intent:
that when a schema defines a unique attribute marked optional (attribute "name"
in the Router schema), the generated ProfileNetworkRouter should not expose that
optional unique attribute and attempting to access it via
Profile.get_attribute("name") should raise a ValueError; place the docstring
immediately below the async def line and use one brief summary sentence followed
by a short "Args" section if desired to document any parameters (none for pytest
tests).

---

Nitpick comments:
In `@backend/infrahub/core/schema/schema_branch.py`:
- Around line 2369-2376: Add a Google-style triple-quoted docstring to the
function check_attr_in_uniqueness_constraint describing its purpose and matching
rules: it checks whether a given attribute name (attr) appears in any
NodeSchema.uniqueness_constraints either exactly or as a prefix for nested
fields using the "attr__field" pattern (i.e., constraint_path == attr or
constraint_path.startswith(f"{attr}__")). Document the parameters (attr: str,
node_schema: NodeSchema), the boolean return value, and a brief example or note
about the expected uniqueness_constraints structure to clarify behavior.

In `@changelog/7644.added.md`:
- Line 1: Update the changelog sentence to explicitly state that attributes are
excluded only when they are both optional and part of uniqueness constraints
(i.e., participate in uniqueness_constraints), not merely when they have
unique=True; mention "optional and participate in uniqueness_constraints" and
optionally reference the `uniqueness_constraints` term and `unique=True` to
clarify the distinction.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f3258911-bbf8-45c5-ba94-67a4c6cbb26a

📥 Commits

Reviewing files that changed from the base of the PR and between dbc08c6 and 8dc7760.

📒 Files selected for processing (3)
  • backend/infrahub/core/schema/schema_branch.py
  • backend/tests/component/core/schema_manager/test_manager_schema.py
  • changelog/7644.added.md

Comment on lines +4667 to +4682
async def test_profile_does_not_contain_optional_unique_attributes() -> None:
schema = {
"namespace": "Network",
"name": "Router",
"uniqueness_constraints": [["name__value"]],
"human_friendly_id": ["name__value"],
"display_label": "name__value",
"attributes": [{"name": "name", "kind": "Text", "optional": True}],
}
schema_branch = SchemaBranch(cache={}, name="test")
schema_branch.load_schema(schema=SchemaRoot(nodes=[schema]))
schema_branch.process()

network_router_profile = schema_branch.get(name="ProfileNetworkRouter", duplicate=False)
with pytest.raises(ValueError, match="Unable to find the attribute name"):
network_router_profile.get_attribute("name")
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.

⚠️ Potential issue | 🟡 Minor

Add docstring to document test purpose.

The test function lacks a docstring explaining its purpose, which makes it harder for other developers to understand what behavior is being tested and why.

📝 Proposed fix to add docstring
 async def test_profile_does_not_contain_optional_unique_attributes() -> None:
+    """Test that optional attributes in uniqueness constraints are excluded from profile schemas.
+    
+    When an attribute is both optional and participates in a uniqueness constraint,
+    it should not be included in the generated profile schema node to prevent
+    profiles from allowing values that would violate uniqueness semantics.
+    """
     schema = {

As per coding guidelines, Python functions should include docstrings following Google-style format with a brief description.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def test_profile_does_not_contain_optional_unique_attributes() -> None:
schema = {
"namespace": "Network",
"name": "Router",
"uniqueness_constraints": [["name__value"]],
"human_friendly_id": ["name__value"],
"display_label": "name__value",
"attributes": [{"name": "name", "kind": "Text", "optional": True}],
}
schema_branch = SchemaBranch(cache={}, name="test")
schema_branch.load_schema(schema=SchemaRoot(nodes=[schema]))
schema_branch.process()
network_router_profile = schema_branch.get(name="ProfileNetworkRouter", duplicate=False)
with pytest.raises(ValueError, match="Unable to find the attribute name"):
network_router_profile.get_attribute("name")
async def test_profile_does_not_contain_optional_unique_attributes() -> None:
"""Test that optional attributes in uniqueness constraints are excluded from profile schemas.
When an attribute is both optional and participates in a uniqueness constraint,
it should not be included in the generated profile schema node to prevent
profiles from allowing values that would violate uniqueness semantics.
"""
schema = {
"namespace": "Network",
"name": "Router",
"uniqueness_constraints": [["name__value"]],
"human_friendly_id": ["name__value"],
"display_label": "name__value",
"attributes": [{"name": "name", "kind": "Text", "optional": True}],
}
schema_branch = SchemaBranch(cache={}, name="test")
schema_branch.load_schema(schema=SchemaRoot(nodes=[schema]))
schema_branch.process()
network_router_profile = schema_branch.get(name="ProfileNetworkRouter", duplicate=False)
with pytest.raises(ValueError, match="Unable to find the attribute name"):
network_router_profile.get_attribute("name")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/component/core/schema_manager/test_manager_schema.py` around
lines 4667 - 4682, Add a Google-style docstring to the test function
test_profile_does_not_contain_optional_unique_attributes describing its intent:
that when a schema defines a unique attribute marked optional (attribute "name"
in the Router schema), the generated ProfileNetworkRouter should not expose that
optional unique attribute and attempting to access it via
Profile.get_attribute("name") should raise a ValueError; place the docstring
immediately below the async def line and use one brief summary sentence followed
by a short "Args" section if desired to document any parameters (none for pytest
tests).

assert pool_rel.peer == InfrahubKind.NUMBERPOOL


async def test_profile_does_not_contain_optional_unique_attributes() -> None:
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.

test should cover uniqueness_constraints with multiple values too

@solababs solababs requested a review from ajtmccarty March 11, 2026 18:51
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
backend/infrahub/core/schema/attribute_schema.py (1)

83-85: Redundant condition in support_profiles.

The clause not (self.optional and self.unique) is always True when self.unique is False (already checked earlier in the expression), making it a no-op. If this was intended to document intent, consider a comment instead. If the logic needs to change, the condition should be revisited.

# Current: always True when self.unique is False
return self.read_only is False and self.unique is False and not (self.optional and self.unique)
# Simplified equivalent:
return self.read_only is False and self.unique is False
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/infrahub/core/schema/attribute_schema.py` around lines 83 - 85, The
support_profiles property contains a redundant condition: "not (self.optional
and self.unique)" is always true when "self.unique is False", so update the
boolean expression in support_profiles to remove that clause (i.e., return
self.read_only is False and self.unique is False) to simplify logic; if the
extra clause was intentional for documentation, instead keep the simplified
expression and add a brief inline comment inside the support_profiles property
explaining the intended constraint involving optional/unique to preserve intent.
backend/infrahub/core/schema/basenode_schema.py (1)

690-697: LGTM with a minor suggestion.

The implementation correctly identifies attributes in uniqueness constraints by matching both exact names and attr__field style paths. Consider adding a brief docstring for clarity.

📝 Optional: Add docstring
 def check_attr_in_uniqueness_constraint(self, attr: str) -> bool:
+    """Check if an attribute participates in any uniqueness constraint.
+
+    Args:
+        attr: The attribute name to check.
+
+    Returns:
+        True if the attribute appears in any uniqueness constraint path.
+    """
     if not self.uniqueness_constraints:
         return False
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/infrahub/core/schema/basenode_schema.py` around lines 690 - 697, Add
a brief docstring to the method check_attr_in_uniqueness_constraint explaining
its purpose and behavior: state that it checks whether a given attribute name
appears in any uniqueness constraint (either as an exact match or as a prefix in
"attr__field" style paths), document the parameter attr: str and the boolean
return value, and note that it returns False when self.uniqueness_constraints is
empty; place the docstring directly above the def
check_attr_in_uniqueness_constraint line.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/infrahub/core/relationship/constraints/profiles_removal.py`:
- Around line 39-43: The condition in the profiles removal check is wrong:
because it requires not attr_schema.optional, the nested clause
attr_schema.optional and schema.check_attr_in_uniqueness_constraint(...) is
unreachable. Update the boolean logic in the function that contains this
condition (the block using attr_schema.support_profiles and
schema.check_attr_in_uniqueness_constraint) so the uniqueness-constraint check
is actually evaluated—for example, remove the contradictory not
attr_schema.optional requirement or rewrite the predicate as
"attr_schema.support_profiles and not (attr_schema.optional and
schema.check_attr_in_uniqueness_constraint(attr=attr_schema.name))" if you want
to allow required attrs unless they are in a uniqueness constraint, or as
"attr_schema.support_profiles and not
schema.check_attr_in_uniqueness_constraint(attr=attr_schema.name)" if required
attrs should also be excluded; adjust the condition in the same function where
attr_schema and schema are referenced.

---

Nitpick comments:
In `@backend/infrahub/core/schema/attribute_schema.py`:
- Around line 83-85: The support_profiles property contains a redundant
condition: "not (self.optional and self.unique)" is always true when
"self.unique is False", so update the boolean expression in support_profiles to
remove that clause (i.e., return self.read_only is False and self.unique is
False) to simplify logic; if the extra clause was intentional for documentation,
instead keep the simplified expression and add a brief inline comment inside the
support_profiles property explaining the intended constraint involving
optional/unique to preserve intent.

In `@backend/infrahub/core/schema/basenode_schema.py`:
- Around line 690-697: Add a brief docstring to the method
check_attr_in_uniqueness_constraint explaining its purpose and behavior: state
that it checks whether a given attribute name appears in any uniqueness
constraint (either as an exact match or as a prefix in "attr__field" style
paths), document the parameter attr: str and the boolean return value, and note
that it returns False when self.uniqueness_constraints is empty; place the
docstring directly above the def check_attr_in_uniqueness_constraint line.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 383dcee9-cb7a-4515-8d76-ba890f3e125b

📥 Commits

Reviewing files that changed from the base of the PR and between cccd25e and 4a7796e.

📒 Files selected for processing (7)
  • backend/infrahub/core/migrations/schema/attribute_supports_generated_schema.py
  • backend/infrahub/core/migrations/schema/node_attribute_add.py
  • backend/infrahub/core/relationship/constraints/profiles_removal.py
  • backend/infrahub/core/schema/attribute_schema.py
  • backend/infrahub/core/schema/basenode_schema.py
  • backend/infrahub/core/schema/schema_branch.py
  • backend/tests/component/core/schema_manager/test_manager_schema.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/tests/component/core/schema_manager/test_manager_schema.py

@ajtmccarty
Copy link
Copy Markdown
Contributor

I think the full fix for this requires keep profile schemas correctly updated when a schema's uniqueness_constraints changes. for example, if I remove an attribute from the uniqueness_constraints of a schema, then that attribute might need to be added to the profile schema associated with the node. we don't currently do this and I think it would require a schema migration for uniqueness_constraints, which would require updating the uniqueness_constraints schema attribute on base_node_schema to UpdateSupport.MIGRATION_REQUIRED
I'm not entirely sure if this is in scope for this change or not, but it is the most correct fix. and we've had to run migrations to clean up this kind of missing profile/template schema attributes issue before

…optional-and-unique-attributes-from-profile-schema-ifc-2048' into sb-11032025-remove-optional-and-unique-attributes-from-profile-schema-ifc-2048
@solababs solababs requested review from a team as code owners March 23, 2026 10:29
@github-actions github-actions bot added the type/documentation Improvements or additions to documentation label Mar 23, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/tests/component/core/migrations/schema/test_node_uniqueness_constraints_update.py (1)

104-267: Solid migration regression coverage; add one case for required attributes.

The suite thoroughly exercises optional-attribute transitions. Please add a no-op test where a non-optional support_profiles attribute is in/out of uniqueness_constraints and assert no profile attribute removal/add migration is executed.

Suggested additional test shape
+async def test_migration_no_change_for_required_attr_in_uniqueness_constraint(
+    db: InfrahubDatabase,
+    default_branch: Branch,
+    car_person_schema: SchemaBranch,
+) -> None:
+    schema = registry.schema.get_schema_branch(name=default_branch.name)
+    prev_car_schema = schema.get(name="TestCar")
+    candidate_schema = schema.duplicate()
+    new_car_schema = candidate_schema.get(name="TestCar")
+
+    # pick a support_profiles attribute that is non-optional in this fixture
+    # then toggle uniqueness membership and assert migration count stays 0
+    migration = NodeUniquenessConstraintsUpdateMigration(
+        previous_node_schema=prev_car_schema,
+        new_node_schema=new_car_schema,
+        schema_path=_make_schema_path("TestCar"),
+    )
+    execution_result = await migration.execute(migration_input=MigrationInput(db=db), branch=default_branch)
+    assert not execution_result.errors
+    assert execution_result.nbr_migrations_executed == 0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/tests/component/core/migrations/schema/test_node_uniqueness_constraints_update.py`
around lines 104 - 267, Add a new test that mirrors the optional-attribute cases
but uses a required support_profiles attribute and asserts no migrations run:
duplicate the pattern in
test_migration_no_change_when_optional_attr_not_affected, obtain prev_car_schema
via registry.schema.get_schema_branch(...).get("TestCar"), create
candidate_schema = schema.duplicate(), modify
new_car_schema.uniqueness_constraints to add/remove "support_profiles__value"
while ensuring support_profiles is non-optional on the schema, instantiate
NodeUniquenessConstraintsUpdateMigration(previous_node_schema=prev_car_schema,
new_node_schema=new_car_schema, schema_path=_make_schema_path("TestCar")),
execute it with MigrationInput(db=db) and assert
execution_result.nbr_migrations_executed == 0 and no errors, and finally call
await assert_attribute_path_status(...) for the ProfileTestCar support_profiles
path expecting it to remain "active".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/infrahub/core/relationship/constraints/profiles_removal.py`:
- Around line 39-43: The current conditional in profiles_removal.py is excluding
required attributes because it combines attr_schema.support_profiles, not
attr_schema.optional and not schema.check_attr_in_uniqueness_constraint(...) —
change the logic so the uniqueness-constraint check only causes exclusion for
optional attributes: only skip removal protection when
attr_schema.support_profiles is true AND attr_schema.optional is true AND
schema.check_attr_in_uniqueness_constraint(attr=attr_schema.name) is true;
update the conditional (referencing attr_schema.support_profiles,
attr_schema.optional and schema.check_attr_in_uniqueness_constraint) accordingly
so required (not optional) attributes remain protected even if they appear in
uniqueness constraints.

---

Nitpick comments:
In
`@backend/tests/component/core/migrations/schema/test_node_uniqueness_constraints_update.py`:
- Around line 104-267: Add a new test that mirrors the optional-attribute cases
but uses a required support_profiles attribute and asserts no migrations run:
duplicate the pattern in
test_migration_no_change_when_optional_attr_not_affected, obtain prev_car_schema
via registry.schema.get_schema_branch(...).get("TestCar"), create
candidate_schema = schema.duplicate(), modify
new_car_schema.uniqueness_constraints to add/remove "support_profiles__value"
while ensuring support_profiles is non-optional on the schema, instantiate
NodeUniquenessConstraintsUpdateMigration(previous_node_schema=prev_car_schema,
new_node_schema=new_car_schema, schema_path=_make_schema_path("TestCar")),
execute it with MigrationInput(db=db) and assert
execution_result.nbr_migrations_executed == 0 and no errors, and finally call
await assert_attribute_path_status(...) for the ProfileTestCar support_profiles
path expecting it to remain "active".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f32c8e6c-9f8e-4a86-8387-02bd7d101796

📥 Commits

Reviewing files that changed from the base of the PR and between 4a7796e and 072b2af.

⛔ Files ignored due to path filters (1)
  • backend/infrahub/core/schema/generated/base_node_schema.py is excluded by !**/generated/**
📒 Files selected for processing (8)
  • backend/infrahub/core/migrations/__init__.py
  • backend/infrahub/core/migrations/schema/node_uniqueness_constraints_update.py
  • backend/infrahub/core/relationship/constraints/profiles_removal.py
  • backend/infrahub/core/schema/definitions/internal.py
  • backend/infrahub/core/schema/schema_branch.py
  • backend/tests/component/core/migrations/schema/test_node_uniqueness_constraints_update.py
  • changelog/7644.added.md
  • docs/docs/reference/schema/validator-migration.mdx
✅ Files skipped from review due to trivial changes (3)
  • changelog/7644.added.md
  • docs/docs/reference/schema/validator-migration.mdx
  • backend/infrahub/core/migrations/init.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/infrahub/core/schema/schema_branch.py
  • backend/infrahub/core/migrations/schema/node_uniqueness_constraints_update.py

Copy link
Copy Markdown
Contributor

@ajtmccarty ajtmccarty left a comment

Choose a reason for hiding this comment

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

not sure if it is in scope for this change or if it should be tracked with another issue (if it doesn't already exist), but the docs say that the same constraint should apply to relationships used in uniqueness_constraints too.

@solababs solababs requested a review from ajtmccarty March 24, 2026 17:09
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@backend/infrahub/core/migrations/schema/attribute_supports_generated_schema.py`:
- Around line 127-138: The previous profile-support check currently uses
self.new_schema; change it to use the pre-update schema snapshot
(previous_schema) when computing previous_profile_support_condition so it
reflects the old constraints: call
previous_schema.check_if_attr_supports_profiles(attribute_schema=self.previous_attribute_schema)
for previous_profile_support_condition and keep new_profile_support_condition
using
self.new_schema.check_if_attr_supports_profiles(attribute_schema=self.new_attribute_schema),
ensuring the if-block comparing previous_profile_support_condition !=
new_profile_support_condition correctly detects uniqueness_constraint changes.

In `@schema/openapi.json`:
- Line 2421: This change edits the generated OpenAPI artifact directly by
modifying the literal mapping "update": "migration_required" in
schema/openapi.json; instead update the original source schema (e.g., the API
model/definition that defines the "update" response or enum) where that value is
defined, then run the project's OpenAPI generation workflow (the generator
script/Makefile task used to produce schema/openapi.json) to regenerate the
JSON; revert any direct edits in schema/openapi.json and ensure the source
definition (the schema file or annotation that controls "update") is the one
updated so the generated artifact reflects the intended change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1954375c-becc-4478-9399-9008aee68dde

📥 Commits

Reviewing files that changed from the base of the PR and between 072b2af and 9b0dcc7.

📒 Files selected for processing (6)
  • backend/infrahub/core/migrations/schema/attribute_supports_generated_schema.py
  • backend/infrahub/core/migrations/schema/node_attribute_add.py
  • backend/infrahub/core/relationship/constraints/profiles_removal.py
  • backend/infrahub/core/schema/basenode_schema.py
  • backend/infrahub/core/schema/schema_branch.py
  • schema/openapi.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • backend/infrahub/core/schema/schema_branch.py
  • backend/infrahub/core/relationship/constraints/profiles_removal.py
  • backend/infrahub/core/schema/basenode_schema.py

"title": "Uniqueness Constraints",
"description": "List of multi-element uniqueness constraints that can combine relationships and attributes",
"update": "validate_constraint"
"update": "migration_required"
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.

⚠️ Potential issue | 🟠 Major

Avoid direct edits in generated OpenAPI artifact.

These updates are in schema/openapi.json, which is generated output. Please apply the change in the source schema definitions and regenerate this file via the project’s generation workflow instead of editing it directly.

As per coding guidelines, "Do not edit schema/openapi.json - this is an auto-generated OpenAPI schema file".

Also applies to: 2707-2707, 3032-3032, 3303-3303, 4632-4632, 5401-5401

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@schema/openapi.json` at line 2421, This change edits the generated OpenAPI
artifact directly by modifying the literal mapping "update":
"migration_required" in schema/openapi.json; instead update the original source
schema (e.g., the API model/definition that defines the "update" response or
enum) where that value is defined, then run the project's OpenAPI generation
workflow (the generator script/Makefile task used to produce
schema/openapi.json) to regenerate the JSON; revert any direct edits in
schema/openapi.json and ensure the source definition (the schema file or
annotation that controls "update") is the one updated so the generated artifact
reflects the intended change.

@solababs
Copy link
Copy Markdown
Contributor Author

not sure if it is in scope for this change or if it should be tracked with another issue (if it doesn't already exist), but the docs say that the same constraint should apply to relationships used in uniqueness_constraints too.

@ajtmccarty what docs? as the attributes of a relationship schema are already handled when checking the attributes

Comment on lines +39 to +43
if not attr.support_profiles or not attr.optional:
continue

previous_in_constraint = self.previous_schema.check_attr_in_uniqueness_constraint(attr=attr.name)
new_in_constraint = self.new_schema.check_attr_in_uniqueness_constraint(attr=attr.name)
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 think this should use check_if_attr_supports_profiles instead of breaking the logic apart

else:
all_queries.append(ProfilesAttributeRemoveMigrationQuery)

# Check template support changes
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 think we might need to do this same update for templates, b/c which attributes are included on a template schema are also affected by the uniqueness_constraints
this could be a separate follow-up issue. but the template problem should probably be fixed in the same release as this profile one

Copy link
Copy Markdown
Contributor Author

@solababs solababs Mar 26, 2026

Choose a reason for hiding this comment

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

@wvandeun please confirm the same behavior should be used for template

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.

If we have the same issue with templates, then yes!

@solababs solababs requested a review from ajtmccarty March 26, 2026 15:12
Copy link
Copy Markdown
Contributor

@ajtmccarty ajtmccarty left a comment

Choose a reason for hiding this comment

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

just some test comments
not sure what is going wrong with the component test failures
it would be a good to add an integration test that confirms the new uniqueness constraint migration is triggered and runs correctly on a schema update that includes a uniqueness constraint change

"""


async def assert_attribute_path_status(
Copy link
Copy Markdown
Contributor

@ajtmccarty ajtmccarty Mar 27, 2026

Choose a reason for hiding this comment

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

  1. the cypher query for this function will skip Nodes that do not have an attribute with the matching name and I think it would want to catch those. the fix is to start with a MATCH on the vertex label and then update the MATCHes in the subqueries to be OPTIONAL MATCH
  2. it should probably also include an r1/2.status DESC in the ORDER BY clauses in the subqueries to make sure that active edges are preferred when edges are added and deleted at the same time
  3. it would also be good to confirm that the to time on the edge is null to confirm that it is still active
  4. looking at this some more, it looks like we do something pretty similar in some other tests. this PR has already getting kind of big, so maybe best to leave those other tests for a different PR

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.

thinking about this a third time, maybe it is all right as is if it is only checking that an attribute was added that did not exist before. if it is testing updates, then it is probably not sufficient, but if it is only testing create it is probably fine

car_profile1_main: Node,
) -> None:
"""Adding nbr_seats to a uniqueness constraint removes it from profile nodes."""
await assert_attribute_path_status(
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 think a cleaner way to handle these assertions is to get the ProfileTestCar schema and verify that it does or does not have the nbr_seats attribute
then get any instances of the ProfileTestCar created for or during the test and check the same thing

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.

actually you might need to test it at the database level if you need to confirm that a new attribute was actually added with a NULL value b/c I think attribute loading will fill in missing attributes if they exist on the schema
he best way to confirm is probably to set an actual value on the new attribute on the template and verify that can be saved and retrieved

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

group/backend Issue related to the backend (API Server, Git Agent) type/documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants