feat(spec): reference generic peers by HFID in object files

MI
2d ago
+1701-26

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 kind with hfid to the server, which uses RelatedNodeInput.kind to 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's used_by whose 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 for kind:.

  • infrahubctl object validate reports 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 kind and hfid

    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 (kind plus data) behave as before. A unit test covers nested objects on a generic peer.

How to review

  1. 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_reference and _resolve_generic_hfid (load-time resolution)
  2. Then read tests/unit/sdk/spec/test_object.py and TestSpecObjectGenericPeer in tests/integration/test_spec_object.py.
  3. docs/docs/python-sdk/topics/object_file.mdx has the new section "Reference a node through a generic relationship".
  4. 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 GenericPeer tests passed.
  • Before the review fixes, the full test_spec_object.py passed (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 added RelatedNodeInput.kind (server commit c4fa580582). 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; kind is passed with hfid so 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_by are skipped.
  • infrahubctl object validate reports 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 than kind and hfid.

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.