fix: enforce uniqueness on computed attributes (closes #7924)#9359
fix: enforce uniqueness on computed attributes (closes #7924)#9359polmichel wants to merge 15 commits into
Conversation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Required computed attributes were never added to the uniqueness check filter because the widening logic only covered optional attributes, so duplicate Jinja2-derived values silently slipped through and could produce duplicate HFIDs. Widen the filter to include computed unique attributes in both grouped and attribute uniqueness checkers, and report the input attributes (e.g. "computed from: model") in the violation message so the user can act on the right field. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… edit Add a third replication test exercising a schema where the computed unique attribute is not the HFID, so the violation surfaces as ValidationError (STANDARD) rather than HFIDViolatedError. Drop the earlier edit to NodeAttributeUniquenessConstraint: that class has no production caller — the NodeConstraintRunner only invokes the grouped checker — so widening its filter had no runtime effect. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The replication tests drive create_node() directly — they exercise the production create path, not the NodeGroupedUniquenessConstraint class in isolation. Move them to backend/tests/component/core/node/ alongside test_template_pool_allocation.py, which is the existing home for create_node integration tests. Drop the now-redundant constraint-level test that overlapped with the create-flow coverage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ation The previous implementation extracted input attribute names from Jinja2 variables with a naive split on '__', which dropped the peer attribute when the variable referenced a relationship path (e.g. 'owner__name__value' collapsed to 'owner'). Reuse the schema-path validation already used by the macro processor so relationship-attribute inputs render as '<relationship>.<attribute>' in the violation message. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract the inline boolean expression into _should_implicitly_check_uniqueness with a docstring that explains why optional or computed unique attributes must be evaluated even when absent from the user-supplied filter list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the violation-message formatting (including the Jinja2 template variable resolution that names input attributes for computed fields) out of NodeGroupedUniquenessConstraint into a dedicated builder class. The builder takes only a SchemaBranch and is therefore unit-testable without a database. Inject it through the constraint constructor; the dependency builder wires up the registry-bound SchemaBranch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lder Add tests/helpers/schema_builders.computed_jinja2_attr to remove the repeated read-only/unique/Jinja2 computed-attribute boilerplate across the computed-uniqueness fixtures, and convert those fixtures from dict form to object form so both the unit and component trees share the builder. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
No issues found across 12 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Shadow auto-approve: would auto-approve. The change fixes a specific bug where computed unique attributes were not enforced for uniqueness, and the implementation is well-contained with targeted logic, good tests, and a dedicated message builder that does not affect other parts of the system.
Re-trigger cubic
Decompose UniquenessViolationMessageBuilder._collect_computed_inputs so each method owns one level: the orchestrator deduplicates across fields, _inputs_for_field resolves one field's Jinja2 template, and _resolve_input_name maps a single path to a user-facing name. Hoist the static path-type mask to a module constant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reshape the three fixtures around the minimal RandomStuff schema from the original report and strip attributes, display labels, and the back-relationship that the assertions never exercise. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| constraint = NodeGroupedUniquenessConstraint( | ||
| db=db, | ||
| branch=default_branch, | ||
| message_builder=UniquenessViolationMessageBuilder( |
There was a problem hiding this comment.
I was wondering if a Noop class could be useful to avoid instantiating this class every time we instantiate NodeGroupedUniquenessConstraint or if this is over-engineered. Not sure of the answer, so I let the code in this state for this iteration.
There was a problem hiding this comment.
0 issues found across 2 files (changes from recent commits).
Shadow auto-approve: would auto-approve. This is a targeted bug fix that extends the uniqueness constraint check to computed attributes, preventing duplicate HFIDs; the logic change is minimal and well-encapsulated, the new message builder is isolated and testable, and comprehensive tests confirm correctness without altering critical...
Re-trigger cubic
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
0 issues found across 1 file (changes from recent commits).
Shadow auto-approve: would auto-approve. This PR fixes a well-scoped bug where computed unique attributes were not being enforced for uniqueness, with a small logic change and thorough test coverage, making it low-risk for production.
Re-trigger cubic
Why
Loading objects whose human-friendly id (or any
unique: truefield) is a computed Jinja2 attribute could create duplicates.Schema used
Submitting four
RandomStuffobjects with the samedescriptionproduced four nodes sharing the same computedname/ HFID, because the uniqueness check silently skipped the computed field.Root cause: the constraint widened its check filter only for optional unique attributes. A required computed attribute is never in the user-supplied data, and the macro populates its value only after the filter was captured, so the check was bypassed.
Closes #7924
What changed
Violates uniqueness constraint 'name' (computed from: model)(orowner.namefor relationship-derived inputs), so the user knows which field to change rather than seeing an opaque reference to a read-only field.What stayed the same
NodeAttributeUniquenessConstraintdeliberately left alone: it is registered as a dependency but not wired into the productionNodeConstraintRunner.How to test
Reproduction from the issue: load the
RandomStuffschema (computed uniquename), then load four objects with identicaldescription. Before: four duplicates created. After: rejected withViolates uniqueness constraint 'name' (computed from: description).Follow-up: complementary SDK-side guard
This PR fixes enforcement at the server boundary, which is the correct authoritative place. A complementary improvement could live in the Python SDK /
infrahubctl object loadto catch the common case before anything is sent to the server.Checklist