From 0f09275c3374be33b11b8157c0fc277a23b9f6b1 Mon Sep 17 00:00:00 2001 From: Guillaume Mazoyer Date: Fri, 17 Jul 2026 14:39:42 +0200 Subject: [PATCH] fix(schema): keep the cached derived-value filter for own-id recomputes The cross-node getter for display labels and human-friendly ids took the cached template for the target kind and overwrote its filter_key in place before returning it. The self getter hands back that same cached instance, so one cross-node recompute flipped the cached filter from `ids` to the relationship filter for good. A later own-id recompute then queried with the relationship filter, matched no node, and left the stored value stale. Return a copy with the overridden filter instead of mutating the cache. --- .../core/schema/schema_branch_display.py | 9 ++-- .../core/schema/schema_branch_hfid.py | 9 ++-- .../test_schema_branch_derived_filter_key.py | 43 +++++++++++++++++++ ...+display-label-hfid-own-id-filter.fixed.md | 1 + 4 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 backend/tests/unit/core/schema/test_schema_branch_derived_filter_key.py create mode 100644 changelog/+display-label-hfid-own-id-filter.fixed.md diff --git a/backend/infrahub/core/schema/schema_branch_display.py b/backend/infrahub/core/schema/schema_branch_display.py index f300091e65c..e2e9a844b32 100644 --- a/backend/infrahub/core/schema/schema_branch_display.py +++ b/backend/infrahub/core/schema/schema_branch_display.py @@ -2,7 +2,7 @@ import hashlib from copy import deepcopy -from dataclasses import dataclass, field +from dataclasses import dataclass, field, replace from typing import TYPE_CHECKING if TYPE_CHECKING: @@ -125,9 +125,10 @@ def get_related_template(self, related_kind: str, target_kind: str) -> TemplateL for applicable_kinds in relationship_trigger.attributes.values(): for relationship_identifier in applicable_kinds: if target_kind == relationship_identifier.kind: - template_label = self.get_template_node(kind=target_kind) - template_label.filter_key = relationship_identifier.filter_key - return template_label + # Copy so the cached template keeps its own-id filter for self recomputes. + return replace( + self.get_template_node(kind=target_kind), filter_key=relationship_identifier.filter_key + ) raise ValueError( f"Unable to find registered template for {target_kind} registered on related node {related_kind}" diff --git a/backend/infrahub/core/schema/schema_branch_hfid.py b/backend/infrahub/core/schema/schema_branch_hfid.py index 8810c2856bf..c1074758414 100644 --- a/backend/infrahub/core/schema/schema_branch_hfid.py +++ b/backend/infrahub/core/schema/schema_branch_hfid.py @@ -2,7 +2,7 @@ import hashlib from copy import deepcopy -from dataclasses import dataclass, field +from dataclasses import dataclass, field, replace from typing import TYPE_CHECKING if TYPE_CHECKING: @@ -111,9 +111,10 @@ def get_related_definition(self, related_kind: str, target_kind: str) -> HFIDDef for applicable_kinds in relationship_trigger.attributes.values(): for relationship_identifier in applicable_kinds: if target_kind == relationship_identifier.kind: - template_label = self.get_node_definition(kind=target_kind) - template_label.filter_key = relationship_identifier.filter_key - return template_label + # Copy so the cached definition keeps its own-id filter for self recomputes. + return replace( + self.get_node_definition(kind=target_kind), filter_key=relationship_identifier.filter_key + ) raise ValueError( f"Unable to find registered template for {target_kind} registered on related node {related_kind}" diff --git a/backend/tests/unit/core/schema/test_schema_branch_derived_filter_key.py b/backend/tests/unit/core/schema/test_schema_branch_derived_filter_key.py new file mode 100644 index 00000000000..870cfcfa6e0 --- /dev/null +++ b/backend/tests/unit/core/schema/test_schema_branch_derived_filter_key.py @@ -0,0 +1,43 @@ +"""Resolving a cross-node derived value must not corrupt the cached own-id filter. + +Display labels and HFIDs cache one template per kind. The self getter and the related (cross-node) +getter share that cached instance, so the related getter must return a copy rather than overwrite the +cached ``filter_key`` - otherwise a later own-id recompute queries with the relationship filter and +silently matches nothing. +""" + +from __future__ import annotations + +from copy import deepcopy + +from infrahub.core.schema.schema_branch import SchemaBranch +from tests.helpers.merge_recompute.dataset import PROFILE_NODE_KIND, PROFILE_PEER_KIND, build_profile_schema + + +def _profile_schema_branch(*, hfid_reads_peer: bool = False) -> SchemaBranch: + schema = deepcopy(build_profile_schema()) + if hfid_reads_peer: + node = next(node for node in schema.nodes if node.kind == PROFILE_NODE_KIND) + node.human_friendly_id = ["name__value", "peer__name__value"] + schema_branch = SchemaBranch(cache={}, name="test") + schema_branch.load_schema(schema=schema) + schema_branch.process() + return schema_branch + + +def test_get_related_template_does_not_mutate_cached_filter_key() -> None: + display_labels = _profile_schema_branch().display_labels + + related = display_labels.get_related_template(related_kind=PROFILE_PEER_KIND, target_kind=PROFILE_NODE_KIND) + + assert related.filter_key == "peer__ids" + assert display_labels.get_template_node(kind=PROFILE_NODE_KIND).filter_key == "ids" + + +def test_get_related_definition_does_not_mutate_cached_filter_key() -> None: + hfids = _profile_schema_branch(hfid_reads_peer=True).hfids + + related = hfids.get_related_definition(related_kind=PROFILE_PEER_KIND, target_kind=PROFILE_NODE_KIND) + + assert related.filter_key == "peer__ids" + assert hfids.get_node_definition(kind=PROFILE_NODE_KIND).filter_key == "ids" diff --git a/changelog/+display-label-hfid-own-id-filter.fixed.md b/changelog/+display-label-hfid-own-id-filter.fixed.md new file mode 100644 index 00000000000..3b5c47de908 --- /dev/null +++ b/changelog/+display-label-hfid-own-id-filter.fixed.md @@ -0,0 +1 @@ +Fixed display labels and human-friendly ids that read across a relationship not refreshing when they were recomputed by their own id. Resolving a cross-node template overwrote the cached own-id filter in place, so a later self recompute queried with the relationship filter, matched no node, and left the stored value stale.