feat(spec): reference generic peers by HFID in object files
Why
Some relationships point to a generic rather than to one node kind. An example is a sub-interface's parent_interface, which can be a physical interface or a port-channel. When the generic has no human-friendly ID (HFID) but the node kinds that inherit from it do, an object file cannot reference an existing peer by its HFID. The SDK sends the HFID without a kind, and the server looks it up on the generic and fails with "schema '' does not have a HFID defined".
The workarounds change the schema only to suit the loader:
- Point the relationship at one node kind. Port-channels can then no longer be parents or tunnel sources.
- Add an HFID to the generic. This forces the HFID's attributes and relationships onto the generic.
Goal: object file authors can reference these peers without changing the schema.
Non-goals: no change to the Infrahub server, and no synchronous version of the object file API, which is asynchronous only today.
What changed
What users will notice:
-
A reference can name the node kind and the HFID:
parent_interface: kind: InterfacesPhysicalInterface hfid: [router1, Gi0/0/3]This works for cardinality-one and cardinality-many relationships. The SDK passes
kindwithhfidto the server, which usesRelatedNodeInput.kindto look up HFIDs on generic relationships. -
When the generic has no HFID, a reference can be the HFID alone, as in
parent_interface: [router1, Gi0/0/3]. The loader searches the kinds in the generic'sused_bywhose HFID has the same number of values. It links the node when exactly one matches. When none or more than one matches, the load stops with an error that names the kinds and asks forkind:. -
infrahubctl object validatereports the reference errors that the schema alone can detect:- a kind that does not inherit from the peer
- an unknown kind
- a kind without an HFID
- the wrong number of HFID values
- keys other than
kindandhfid
Validation does not check that the peer exists, because the peer can be created earlier in the same load. A missing or ambiguous peer is reported at load time.
What stays the same:
- References to node peers, references to generics that have an HFID, UUID references, and nested objects (
kindplusdata) behave as before. A unit test covers nested objects on a generic peer.
How to review
- Start with
infrahub_sdk/spec/object.py, which holds all logic changes:get_relationship_info(how values are classified)_validate_kind_hfid_reference(schema checks)_clean_referenceand_resolve_generic_hfid(load-time resolution)
- Then read
tests/unit/sdk/spec/test_object.pyandTestSpecObjectGenericPeerintests/integration/test_spec_object.py. docs/docs/python-sdk/topics/object_file.mdxhas the new section "Reference a node through a generic relationship".dev/specs/002-generic-peer-hfid/holds the spec, plan, tasks and implementation report. These files are not code.
Parts that need extra scrutiny:
- A plain string reference (not a UUID) to a generic without an HFID used to be sent as an
id. It is now resolved as an HFID and fails if nothing matches. It is not known whether the server accepted the old form. - An HFID-alone reference makes one query per candidate kind and caches nothing. A code comment notes this limit.
Alternatives considered (see research.md):
- A prefixed string such as
Kind:router1/Gi0/0/3. Rejected because names can contain/and:. - A file-level parameter that maps each relationship to one kind. Rejected because one file can mix peer kinds.
- Resolving the reference on the server. Out of scope.
How to test
uv run pytest tests/unit/sdk/spec/ tests/unit/ctl/object/ -q
uv run pytest tests/integration/test_spec_object.py -k GenericPeer -v
Local results after the review fixes:
- Unit tests: 104 passed.
- Integration tests: the 4
GenericPeertests passed. - Before the review fixes, the full
test_spec_object.pypassed (16 tests).
Impact & rollout
- Backward compatibility: existing object files load as before, except for the non-UUID string case described under "How to review".
- Server version: references with
kind, and HFID-alone references to generics, need Infrahub 1.1.0 or later. That release addedRelatedNodeInput.kind(server commitc4fa580582). The compatibility page still lists 0.16.x and 1.0.x, and this PR does not change it. - Performance: no extra queries for references that name a kind.
Checklist
- Tests added/updated
- Changelog entry added (
changelog/+generic-peer-hfid.added.md) - External docs updated (
docs/docs/python-sdk/topics/object_file.mdx) - Internal .md docs updated (not done; the knowledge extraction is still pending)
Summary by cubic
Lets object files reference existing nodes through relationships whose peer is a generic, by giving the node's kind and hfid together. Previously these peers could only be referenced through schema workarounds: narrowing the relationship to one node kind or adding an HFID to the generic.
Behavior
- References can be
{kind: <NodeKind>, hfid: [...]}on cardinality-one and cardinality-many relationships;kindis passed withhfidso the server resolves the generic peer against the named kind. - When the generic has no HFID, an HFID-alone reference is looked up among the node kinds that inherit from the generic, and the matching node's id is sent, so released servers fill computed attributes without another lookup. The load fails with a message naming the candidate kinds when zero or more than one match.
- When the generic has a default filter, a string reference is sent as before. Profile and template kinds in the generic's
used_byare skipped. infrahubctl object validatereports schema-detectable errors: a kind that does not inherit from the peer, an unknown kind, a kind without an HFID, a wrong number of HFID values, and keys other thankindandhfid.
Rollout and compatibility
- Existing object files load as before, except non-UUID string references to a generic without an HFID, which are now resolved as HFIDs and fail when nothing matches instead of being sent as an
id. - Kind-plus-HFID references need Infrahub 1.1.0 or later, which added
RelatedNodeInput.kind; HFID-alone references on generics do not, because the SDK sends the resolved id.