fix: attribute source/owner on relationships in update_node - #143
Conversation
update_node stamped source/owner metadata on updated attributes but not on
updated relationships, so a relationship changed by a sync carried no lineage
back to the sync source/owner. This was asymmetric with the create path, which
attributes relationships via generate_payload_create.
Assign relationships via a data dict ({"id", "source", "owner"}) instead of the
bare peer, in both the cardinality-one and cardinality-many branches, so the
same attribution the attribute path applies is stamped on relationships too.
Adds unit tests covering attribute attribution (regression), cardinality-one
and cardinality-many relationships (with and without source/owner), and a
real-SDK check that the dict serialises to _relation__source/_relation__owner.
Fixes #142
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
WalkthroughAdded a 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
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. Comment |
Deploying infrahub-sync with
|
| Latest commit: |
caaa32c
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://052a913d.infrahub-sync.pages.dev |
| Branch Preview URL: | https://fix-update-node-relationship.infrahub-sync.pages.dev |
estivate
left a comment
There was a problem hiding this comment.
Approve. Verified end-to-end against a live Infrahub 1.10.6 (Docker stack, throwaway schema, real GraphQL):
| attribute | rel (one) | rel (many) | |
|---|---|---|---|
main |
source+owner | — | — |
| this PR | source+owner | source+owner | source+owner |
Idempotent across re-runs. Protected relationships (created via generate_payload_create(is_protected=True)) still update fine — no new failure mode. Post-merge with main: 122 passed / 3 skipped, ruff clean; the 3 ty warnings are pre-existing on main.
The approach is right: generate_payload_create emits exactly this dict shape for relationships, and RelatedNodeBase.__init__ nulls all properties when handed a peer node — which is precisely the bug.
Please fix
- Docstring is false. It claims parity with the create path, but create also sets
is_protected=True. Contradicts your own Note — drop the clause. - Trim the comments. 7-line docstring + 2 duplicate call-site comments state the same thing 3×. See
9c571f7("drop narrating comments where code is self-explanatory") on this file. - Document: no backfill. Only newly-linked peers get attribution. Pre-existing links never converge on re-sync — operators will think the fix didn't work.
- Rebase — 26 commits behind (merges clean).
Nits
test_update_node_attribute_gets_source_and_ownerassertsis not None— a source/owner swap stays green. Pin exact values like the relationship tests do.- Cardinality-one tests assert on
FakeNode.__dict__; realInfrahubNodeSync.__setattr__intercepts that, so they can't fail for the intended reason. The two real-RelatedNodeSynctests carry the real coverage. FakeAttr/FakeSchema/FakeNode+ ty wrapper duplicatetest_infrahub_node_to_diffsync.py→tests/adapters/conftest.py.- Line 173
list(attr_value)has noNoneguard, unlikediffsync_to_infrahub. Generated fields default to[], so hardening only.
Behavior change worth a line in the description
- Cardinality-one loses resource-pool handling: dict →
_peer=None→is_resource_poolalwaysFalse(verified: dictFalse, peerTrue), soallocate_from_poolnever fires and a pool peer gets linked rather than allocated from. Unreachable today (no pool kinds inexamples/), and create already has this shape — so this makes update consistent with an already-broken create.
Separate issue, not this PR
existing_peer_idsis read before the conditionalfetch()(line 170). For default-Generic-kind many relationships — whichgenerate_query_datadoesn't prefetch — the manager arrivesinitialized=Falsewithpeer_ids == [], soexisting_onlyis empty and stale peers are never removed. Pre-existing onmain, independent of attribution. (New peers still get attributed correctly, so it doesn't undermine this fix.)
Note on coverage: the e2e above used kind: Attribute relationships, so it did not exercise the Generic path in item 10.
🤖 Review prepared with Claude Code
…ionship-attribution
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>
|
Review feedback addressed in the latest branch:\n\n- corrected the cardinality-one implementation to retain the resolved peer and preserve resource-pool allocation\n- replaced fake cardinality-one coverage with real SDK nodes and pinned exact source/owner values\n- trimmed inaccurate/redundant narration and documented the no-backfill boundary\n- merged current |
Problem
update_node(infrahub_sync/adapters/infrahub.py) setssource/ownerattribution on updated attributes but not on updated relationships. A relationship changed by a sync therefore carries no lineage back to the sync's source/owner — most visible for update-only flows, where an object is matched and only its relationships/attributes are updated.Fixes #142.
Fix
Apply source/owner attribution to both relationship update paths:
Retaining the peer object in the cardinality-one path is important: the SDK uses it to recognize
CoreResourcePoolnodes and emitfrom_poolallocation input.source/ownerare omitted when unset, so behavior is unchanged when no attribution is configured.This change attributes newly linked cardinality-one peers and newly added cardinality-many edges. It intentionally does not rewrite or backfill unchanged pre-existing edges. It also does not change relationship protection behavior.
Tests
tests/adapters/test_infrahub_update_node_attribution.pynow contains 13 focused regression tests covering:InfrahubNodeSync/RelatedNodeSyncserialization for cardinality-onefrom_poolallocationValidation on the current branch:
infrahub-sync generateagainst the live test schema passesThe branch includes the current
mainhistory via a merge commit, per the repository's no-force-push policy.🤖 Updated with Codex
Summary by CodeRabbit