Skip to content

scope uniqueness constraints to specific objects - #10053

Open
ajtmccarty wants to merge 33 commits into
developfrom
ifc-2796-node-scope-uniqueness-full
Open

scope uniqueness constraints to specific objects#10053
ajtmccarty wants to merge 33 commits into
developfrom
ifc-2796-node-scope-uniqueness-full

Conversation

@ajtmccarty

@ajtmccarty ajtmccarty commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

combines two previous reviewed PRs

Only these commits (the final 3) are unreviewed

Each of these is self-contained and can be reviewed on its own:

  • 671b39c — reject a non-positive chunked() size
    chunked() let size=0 surface range()'s own opaque ValueError and silently yielded nothing for a negative size. Adds an explicit guard and replaces the single test with rejection cases for zero/negative plus round-trip cases (empty, exact multiple, trailing partial, oversized size, tuple-in/tuple-out). Defensive only — every current caller passes a positive size.

  • dbbcad4 — move get_query_arrows() from RelationshipSchema to Query
    Mapping a relationship direction to Cypher arrows is a database concern, not a schema one. QueryArrow/QueryArrows and the mapping now live in infrahub.core.query as Query.get_query_arrows(direction); all nine call sites were already inside Query subclasses, and QueryArrow* had no consumers outside relationship_schema.py. Pure refactor — the arrows rendered per direction are unchanged.

  • abcd7e7 — traverse the relationship in its own direction when resolving dependents
    AffectedUniquenessDependentsQuery matched IS_RELATED undirected, so for a kind whose relationship peer is its own kind (parent/children sharing one identifier) resolving the dependents of a changed peer returned nodes on both sides, over-scoping validation. Threads RelationshipDirection through the scoper and resolver so the path is traversed node→Relationship→peer, matching how the edges are created and how the sibling uniqueness queries already read them. Currently latent — a peer-attribute constraint element still falls back to full-population validation — but it matters as soon as targeted validation covers those paths. Adds self-referential coverage that fails against the undirected query.

Review in cubic

ajtmccarty and others added 30 commits July 21, 2026 18:43
queries for node-scoped uniqueness constraints
…niquenessChecker

Replace the per-node targeted uniqueness path with a single batched
TargetedUniquenessValidationQuery per constraint group, paged over the changed
nodes. Take branch and schema_branch from the validator request rather than the
constructor, and open one read-only session per change instead of per query.

Update the dependency builder and the m018 migration for the new signatures, and
switch the node-scoped tests to exact violation-set assertions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The constraint result model gained a node_uuids field, so model_dump()
now emits 'node_uuids': None. Update the two schema-validate tests whose
hardcoded expected dicts predate the field.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The targeted validation path emitted attribute values with their native
graph type, so an integer attribute rendered as value=8 while the
full-population path rendered '8'. Stringify the value when building the
data path so error messages are identical across both paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The determiner re-initializes its node-diff index on each get_constraints
call, but the uniqueness scoper's per-kind scope cache was never cleared,
so a reused determiner could return scopes computed from a previous diff.
Add a reset() on the scoper and call it alongside index initialization.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The batched targeted query cannot read an attribute of a related peer
(a constraint element such as owner__name) and raises on one. Detect
such constraints and validate the whole population for that schema
instead of scoping to the changed nodes, since the full-population path
supports them. Removes the xfail markers on the two peer-attribute tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The diff field summary collapsed all changed node uuids per kind, so
changing one node's unique field validated every changed node of that
kind. Track uuids per changed field through NodeDiffFieldSummary, the
diff query, NodeDiffIndex, and the uniqueness scoper, so a uniqueness
check is scoped to only the nodes whose changed field participates in it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scope uniqueness constraints to affected objects
@github-actions github-actions Bot added the group/backend Issue related to the backend (API Server, Git Agent) label Jul 27, 2026
@codspeed-hq

codspeed-hq Bot commented Jul 27, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 12 untouched benchmarks


Comparing ifc-2796-node-scope-uniqueness-full (abcd7e7) with develop (fe8fb2c)

Open in CodSpeed

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

2 issues found across 48 files

Confidence score: 3/5

  • In backend/infrahub/core/diff/query/field_summary.py, the targeted uniqueness subqueries drop diff_root, so validation can mix data across concurrent unmerged diffs and produce incorrect uniqueness outcomes (false conflicts or missed conflicts) for the selected change set — preserve/apply diff_root in those targeted subqueries so checks stay scoped to the enriched diff.
  • In backend/infrahub/proposed_change/tasks.py, relationship-attribute uniqueness still falls back to an unbounded full-population scan because UniquenessChecker rejects targeted paths, which can both inflate runtime and validate against more data than intended during data-triggered updates — enable targeted validation for these paths (per IFC-2796) to keep checks bounded to affected node IDs.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/infrahub/proposed_change/tasks.py">

<violation number="1" location="backend/infrahub/proposed_change/tasks.py:627">
P2: Relationship-attribute uniqueness changes still run the unbounded full-population scan because `UniquenessChecker` rejects targeted validation for those paths. According to linked Jira issue IFC-2796, data-triggered uniqueness validation must be scoped to affected IDs; extend targeted validation for these paths or avoid this fallback.</violation>
</file>

<file name="backend/infrahub/core/diff/query/field_summary.py">

<violation number="1" location="backend/infrahub/core/diff/query/field_summary.py:59">
P1: According to linked Jira issue IFC-2796, targeted uniqueness checks must receive only affected node IDs from the selected enriched diff. These subqueries discard `diff_root`, so concurrent unmerged diffs of the same kind contribute UUIDs and can trigger unrelated validation (and restore work proportional to other diffs); scope each re-match through the selected root.</violation>
</file>

Shadow auto-approve: would not auto-approve because issues were found.

Re-trigger cubic

AND a.action <> $unchanged_str
WITH DISTINCT a.name AS attr_name
RETURN collect(attr_name) AS attr_names
WITH a.name AS attr_name, collect(DISTINCT n.uuid) AS attr_node_uuids

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.

P1: According to linked Jira issue IFC-2796, targeted uniqueness checks must receive only affected node IDs from the selected enriched diff. These subqueries discard diff_root, so concurrent unmerged diffs of the same kind contribute UUIDs and can trigger unrelated validation (and restore work proportional to other diffs); scope each re-match through the selected root.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/infrahub/core/diff/query/field_summary.py, line 59:

<comment>According to linked Jira issue IFC-2796, targeted uniqueness checks must receive only affected node IDs from the selected enriched diff. These subqueries discard `diff_root`, so concurrent unmerged diffs of the same kind contribute UUIDs and can trigger unrelated validation (and restore work proportional to other diffs); scope each re-match through the selected root.</comment>

<file context>
@@ -47,30 +56,43 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None:  # noqa
             AND a.action <> $unchanged_str
-            WITH DISTINCT a.name AS attr_name
-            RETURN collect(attr_name) AS attr_names
+            WITH a.name AS attr_name, collect(DISTINCT n.uuid) AS attr_node_uuids
+            WHERE attr_name IS NOT NULL
+            RETURN collect({name: attr_name, node_uuids: attr_node_uuids}) AS attr_name_uuids
</file context>

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.

this one is fixed on stable in #10054

Comment thread backend/infrahub/core/validators/uniqueness/query/affected_dependents.py Outdated
determiner = ConstraintValidatorDeterminer(schema_branch=schema)
return await determiner.get_constraints(node_diffs=list(node_diff_field_summary_map.values()))
async with db.start_session(read_only=True) as session_db:
determiner = build_constraint_validator_determiner(db=session_db, branch=branch, schema_branch=schema)

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.

P2: Relationship-attribute uniqueness changes still run the unbounded full-population scan because UniquenessChecker rejects targeted validation for those paths. According to linked Jira issue IFC-2796, data-triggered uniqueness validation must be scoped to affected IDs; extend targeted validation for these paths or avoid this fallback.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/infrahub/proposed_change/tasks.py, line 627:

<comment>Relationship-attribute uniqueness changes still run the unbounded full-population scan because `UniquenessChecker` rejects targeted validation for those paths. According to linked Jira issue IFC-2796, data-triggered uniqueness validation must be scoped to affected IDs; extend targeted validation for these paths or avoid this fallback.</comment>

<file context>
@@ -610,18 +612,20 @@ async def _get_proposed_change_schema_integrity_constraints(
-    determiner = ConstraintValidatorDeterminer(schema_branch=schema)
-    return await determiner.get_constraints(node_diffs=list(node_diff_field_summary_map.values()))
+    async with db.start_session(read_only=True) as session_db:
+        determiner = build_constraint_validator_determiner(db=session_db, branch=branch, schema_branch=schema)
+        return await determiner.get_constraints(node_diffs=list(node_diff_field_summary_map.values()))
 
</file context>

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.

using relationship-attribute uniqueness constraints is currently prevented when loading/updating a schema, so we do not need to support them at this point

Comment thread backend/infrahub/utilities/chunks.py

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

1 issue found across 14 files (changes from recent commits).

Confidence score: 5/5

  • In backend/infrahub/core/query/__init__.py, QueryArrowBidir appears unused while get_query_arrows() routes bidirectional behavior through QueryArrowOutbound/QueryArrowInbound, which creates a maintenance risk by implying a code path that doesn’t exist—remove QueryArrowBidir (or wire it in intentionally) to keep query-arrow behavior unambiguous.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/infrahub/core/query/__init__.py">

<violation number="1" location="backend/infrahub/core/query/__init__.py:160">
P3: `QueryArrowBidir` is unused: bidirectional paths use `QueryArrowOutbound`/`QueryArrowInbound` in `get_query_arrows()`, and no other code imports or instantiates it. Removing it avoids maintaining a misleading unused representation.</violation>
</file>

Shadow auto-approve: would not auto-approve because issues were found.

Re-trigger cubic



@dataclass
class QueryArrowBidir(QueryArrow):

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.

P3: QueryArrowBidir is unused: bidirectional paths use QueryArrowOutbound/QueryArrowInbound in get_query_arrows(), and no other code imports or instantiates it. Removing it avoids maintaining a misleading unused representation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/infrahub/core/query/__init__.py, line 160:

<comment>`QueryArrowBidir` is unused: bidirectional paths use `QueryArrowOutbound`/`QueryArrowInbound` in `get_query_arrows()`, and no other code imports or instantiates it. Removing it avoids maintaining a misleading unused representation.</comment>

<file context>
@@ -138,6 +138,36 @@ def __str__(self) -> str:
+
+
+@dataclass
+class QueryArrowBidir(QueryArrow):
+    start: str = "-"
+    end: str = "-"
</file context>

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.

feel incomplete not to include it

@ajtmccarty
ajtmccarty marked this pull request as ready for review July 27, 2026 23:11
@ajtmccarty
ajtmccarty requested a review from a team as a code owner July 27, 2026 23:11
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) review/release-1.11

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant