Release 1.2.8
| Release Number | 1.2.8 |
|---|---|
| Release Type | Feature |
| Release Date | September 10th, 2026 |
| Tag | v1.2.8 |
Release summary
After upgrading, you can convert NetBox device-type definitions into Infrahub Object Templates, learn Infrahub concepts through your own repository and instance, triage a diagnostic bundle before escalating it, and report a skill's own missing guidance as a reviewed issue. The schema, check, transform, and generator guidance also changed: what a declaration on a generic imposes on every inheriting kind is now stated explicitly, and every infrahubctl command the skills print has been checked against the CLI.
Most of what changed is what the skills know rather than how you invoke them, so existing schema, object, check, transform, and generator work continues unchanged. One item needs an edit on your side.
- Refresh any vendored copy of these skills; a stale copy may still print
infrahubctlcommands that do not exist.
See Upgrade notes for full details.
Convert NetBox device-type definitions into Infrahub Object Templates
Turn NetBox device-type and module-type files — the netbox-community/devicetype-library format — into Infrahub Object Templates with the new converting-netbox-device-types skill. A bundled Python converter reads a mapping profile describing your target schema and writes manufacturers, device types, module types, and component templates as object YAML, alongside a coverage report naming everything it could not map (#80).
- Convert a single file, a folder, or a mixed tree of device types and module types in one pass. The two families are told apart by
slug, which every device type carries and no module type does, so a mixed tree does not have to be split up first. Output files are numbered01_manufacturersthrough05_module_templatessoinfrahubctl object loadruns them in dependency order. - Point the converter at your own schema with a mapping profile rather than editing the script. Every Infrahub kind, attribute, and relationship name is read from a YAML profile, so a custom schema needs a new profile instead of a forked converter. Three profiles ship:
schema-library.yml,schema-library-modules.yml, and an annotated_template.yml. - Read the Markdown coverage report before loading anything. Anything the profile cannot map is skipped and named rather than dropped silently, and against the stock
schema-library.ymlprofile no published device type converts losslessly, because schema-library models no equivalent node for most NetBox component lists. Turn each report line into a schema change with the worked YAML inextending-your-schema.md— the skill offers the change rather than making it, since enablinggenerate_templateis a migration against your source of truth. - Create the real interfaces behind a
{module}token with the bundled module-port generator. The token cannot be resolved at conversion time, so a template carries it and the generator resolves it per installed module after the templates are loaded.
Learn Infrahub concepts through your own repository and instance
Work through Infrahub concepts — foundations, schema, objects, GraphQL, branches, repository integration, proposed changes, checks, transforms, generators, and menus — with the new teaching-concepts skill. It asks what you already know before it explains anything, and teaches from your own repository files and read-only instance queries rather than from generic examples (#118).
- Resume a session instead of restarting it. Progress, lessons, and exercises are recorded in a
.infrahub-learning/workspace, and each lesson is written tolessons/<concept>.mdin a fixed Probe, Explain, Exercise, Check shape that doubles as the durable learning record. - Follow a dependency-ordered map of eleven concepts. Each carries its own probe questions, exercise specification, verification method, documentation anchors, and a graduation pointer to the skill that does the same work for real.
- Attempt an exercise knowing it is solvable. The reference solution is written and verified — through offline schema validation, a read-only query, or an
infrahubctlrun — before the exercise is shown, and a hint ladder runs before any solution is revealed. - Keep your instance safe while learning. Read-only access is the default, authoring exercises are done in local files, and instance writes need an explicit opt-in, run only in a
learning-*branch, are never merged, and are always deleted. Nothing writes to the default branch.
Triage a diagnostic bundle before escalating it
Read a bundle collected by collecting-diagnostics yourself with the new analyzing-diagnostics skill. It reads the manifest first, sweeps every service's logs for tracebacks, ERROR and CRITICAL lines, OOM kills, and *.previous.log restart evidence, then reports correlated incidents with root causes separated from cascades rather than a flat list of errors (#76).
- Find out whether your crash is already known, and possibly already fixed, before anyone files an issue or waits on support. Findings are matched against existing
opsmill/infrahubissues using stable search keys — exception class plus normalized message — with volatile tokens such as branch names, UUIDs, and hostnames stripped so a known issue actually matches. A match then resolves against the version the bundle reports running: a newer fix version means upgrade, and already running the fix means an unconfirmed match or a possible regression. - Read every finding against evidence you can check. Each claim cites a bundle path with a quoted excerpt, the report opens with the deployment context — running version, topology, replica counts — and what the bundle cannot answer is reported as an open question mapped to the
infrahub-collect createflags a next bundle would need. - Search targeted failure patterns instead of generic ones. A known-failure-patterns reference covers Prefect background services disabled by edited Helm values, lock deadlocks after crashed merges, stale RUNNING tasks, and storage-persistence and multi-replica shared-storage misconfigurations, each mapped to where its evidence sits in the bundle.
- Keep the analysis read-only toward your deployment. It never files anything: filing hands off to reporting-issues, a second collection hands back to collecting-diagnostics, and both diagnostics skills now frame
--include-backupas a last resort, with a caution about restored backups carrying live git repository connections.
Report a skill's missing or wrong guidance as a reviewed issue
When an Infrahub skill's guidance is missing or wrong, the failure is quiet — extra requests and repeated corrections, never a crash. Diagnose it with the new reporting-skill-gaps skill, which works out which rule file is at fault and what it should say instead, then hands the draft to reporting-issues for everything GitHub-facing (#83, #119).
- Rest a report on evidence rather than an impression. Four probes run strongest first: a verifier that failed and later passed, a coverage read of the skill's
rules/naming the file or its absence, the difference between the rejected and the accepted artifact, and retry counts. A draft resting only on retry counters is refused, and the refusal names the probe that came up empty. - See the report classified before it is filed. A case a rule covers where the guidance still produced the wrong answer is a bug; a case no rule covers where the documentation supplied the answer is a feature request; a case no rule covers where the documentation did not is a documentation gap. The discriminator is what happened after the escape to documentation, not whether documentation existed.
- Read which revision of the guidance failed from the report header, which records the skills-plugin version alongside the Infrahub SDK and Infrahub versions. All three come from one
infrahubctl infocall, andunknownandn/aare accepted because offline work never reaches a server. - Keep diagnosis and filing separate. This skill never files and never names a target repository — reporting-issues resolves that from the report type against the registry it already owns — and the tracker search it ran is passed across, so the search is not repeated and answered differently.
Know what a generic imposes on every kind that inherits it
Declaring something on a generic behaves differently from declaring it on a kind, and until now managing-schemas described the syntax without the semantics. Model with generics knowing what each declaration imposes on every inheriting kind: every rule below quotes the load-time error its violation produces, proven against Infrahub's in-memory schema validator rather than inferred from reading code (#85, #111, #112, #113).
- Scope a
uniqueness_constraintsentry before you declare it. A constraint on a generic is enforced across every kind that inherits it, and a concrete kind cannot narrow what it inherits — declaring its own constraints only adds a kind-scoped check while the generic's still runs. That decides a migration: on the generic, old and new instances collide and deletion becomes an ordering precondition; on the concrete kinds they coexist and the migration stays reversible. - Meet the three preconditions a relationship named in a
uniqueness_constraintsentry has to satisfy —optional: false,cardinality: one, and the bare relationship name with no peer-attribute path — each with its verbatim error string and a loadable example. Ahuman_friendly_idis compiled into a uniqueness constraint too, so an optional relationship reached by an HFID path fails schema load with auniqueness_constraints:error that names a field you never wrote. - Override a
Dropdownattribute on an implementer without widening it by accident. A shorterchoiceslist loads cleanly, and so does a longer one carrying an invented choice, so a concrete kind can hold a value the generic says is impossible and a query over the generic can return it. Guard it with a test comparing each restated list against the generic's, or withallow_override: noneon the generic's attribute, which rejects any override and trades away per-kind defaults. - Treat a generic's implementer set as an interface its consumers already read. A brand-new kind inheriting a generic produces no migration and no constraint validation, so every offline gate stays green while queries, groups, menus, and constraints declared on the generic include the new kind. An offline test over the schema YAML asserting the expected implementer set is the gate a contributor can break, and it runs in under a second.
- Order results within what a generic can resolve.
order_byon a generic resolves against that generic's own declarations only, andnode_metadata__created_atandnode_metadata__updated_atstay valid even on a generic that declares no attributes at all. The error may name an implementer rather than the generic, because the value is copied down to every kind that declares none of its own — so when a kind whose file contains noorder_byis named, look at its generics.
Change a relationship's cardinality without breaking the queries that read it
Setting cardinality: one, hitting a write-time failure on the second object, and widening the relationship is a common sequence, and its second half breaks every stored query that selects that relationship. Both halves are now documented across the schema and query rules, read from Infrahub v1.10.8 source with every schema outcome reproduced by running that checkout's validator (#114).
- Widen the side that actually holds the cap. Your own
cardinality: onelimits what you hold; how many objects may point at a peer is set by that peer's reciprocal declaration on the same identifier, and where the peer declares nothing there is no inbound cap at all. Widening only your own side loads cleanly and leaves the write-time failure exactly where it was. - Rewrite the query the change invalidates.
onegeneratesNestedEdged<Kind>, selected as{ node { … } };manygeneratesNestedPaginated<Kind>, selected as{ edges { node { … } } }withcount. Widening leaves a stored query selecting{ node … }against a now-paginated field, which returnsCannot query field 'node' on type 'NestedPaginated<Kind>'— and that type name appears in no schema file,.gqlfile, or documentation page, so the error is hard to connect to the cardinality edit that caused it. - Express a genuine cap of one with
cardinality: one.max_count: 1on a cardinality-many relationship is rejected outright. A hierarchical kind is the exception to all of this: its generated fields ignore what you declared, withparentalways node-shaped andchildren,ancestors, anddescendantsalways edges-shaped.
Reuse a schema the marketplace already publishes
Check whether the Infrahub marketplace already publishes a domain before modelling it. A workflow step in managing-schemas checks the catalog first, a shared reference in infrahub-common gives every skill the same view of it, and an audit rule in auditing-repo flags a whole domain hand-rolled from scratch when the marketplace publishes it (#69, #113).
- Search the whole catalog for any domain with
infrahubctl marketplace list,search, andshow, and fetch withinfrahubctl marketplace get, including collections with-c. The catalog API covers the discovery the CLI does not expose, and--marketplace-urlpoints at an internal mirror; reachability never blocks schema work, since the guidance falls back to a custom schema built on built-in primitives. - Tell the three kinds of availability apart before building on one. Platform core kinds (
Core*,Builtin*,Ipam*) exist on a clean instance; marketplace-published kinds do not, and become a dependency your repository carries, loads first, and records a version for; locally defined kinds are yours. Check withinfrahubctl schema liston the cleanest instance you have rather than the development machine that has accumulated whatever was loaded during experiments — a kind appearing in an example is evidence of a sensible shape, not evidence it exists on your instance. - Evaluate a published file per generic rather than per file. The cost of a candidate is its transitive peer set, so a sibling with expensive peers is no reason to reject a cheap candidate in the same file, and rejecting per file is how a published shape gets reinvented. Taking a subset also loses the
marketplace getupdate path, so the rule prefers the whole file when the cost is comparable, and asks for a provenance comment recording the identifier, the version, what was taken, and why the rest was excluded. Before adopting a generic from a published file, read what its implementer set imposes on you in Know what a generic imposes on every kind that inherits it.
Write checks, transforms, and generators against verified behavior
Field reports kept describing one class of problem: a mechanism described correctly in one place and contradicted everywhere an example appeared. The checks, transforms, and generators skills now carry the behavior each pattern depends on, verified against Infrahub source, the SDK, and the runtime image (#81, #89, #115, #116, #117).
- Declare a
watchblock on everypython_transformsandgenerator_definitionsentry, includingfiles: []when there is nothing beyond the entry point. Infrahub never scans the imports, so with no key it folds the commit id into the definition's fingerprint and the definition re-renders or re-runs on every commit; a present key is your assertion that the list is complete.jinja2_transformsare the exception, because their closure comes from parsing the template. Nowatchis noisy but safe, and a wrongwatchis silent — declaring anything switches off the conservative fallback, and an incomplete list then under-regenerates with no error anywhere. - Validate a node against a related node's state in a check by traversing the relationship in the query itself. A check runs its query once and
validate()receives that single payload with no lazy fetch, so a query selecting only the child has no parent state left to test. The rule covers unwrapping each hop null-safely, surfacing an unresolvable related node as a violation rather than a silent skip, and exempting the root of a containment chain, which has no parent. - Read GraphQL failures from the response body, not the status code. The server hard-codes
status_code=200on every response whose document executed and puts constraint violations, unknown fields, and permission denials in the body'serrorsarray.self.client.execute_graphqlraisesGraphQLErroron those, so a check using the SDK client is not exposed; the trap is rawhttpxagainst/graphqlbranching on the status code. Test the failing path — a check that fails open cannot be told apart from a passing check by observation. - Constrain a path traversal in a generator with
relationship_filter, which takes schema relationship identifiers such asdevice__interfacerather than the per-side names that appear in the result.kind_filteris a whitelist of node kinds and still lets a shared catalog node of an allowed kind bridge unrelated subgraphs;included_kindsre-includes kinds excluded by default and is not a whitelist. Checktruncated_at_depthin the result, since a non-null value means the search ran out of budget and the answer is incomplete, and note thatshortest_paths_onlydefaults totrueand drops longer routes through the same intermediate objects. - Write a group membership from the side whose peer can be resolved by name.
CoreGroup.memberspeersCoreNode, which has no attributes, nodefault_filter, and nohuman_friendly_id, so a member name has nothing to resolve against;member_of_groupspeersCoreGroup, which does havedefault_filter: name__value. The general rule: a relationship whose peer is a bare generic cannot be resolved by name in object data. A generator's target group must also exist and be non-empty, because an existing but empty group dispatches nothing and reports no error. - Emit a non-text artifact from a transform. A complete
image/svg+xmlexample now sits alongside the content-type table — query, transform,python_transformsregistration,artifact_definitionsentry with its target group, and theCoreArtifactTargetrequirement — and the serialization rule is stated as a matrix rather than a return-type column: onlyapplication/jsonandapplication/yamlspecial-case a returned dict, so returning a dict fortext/csvorimage/svg+xmlwrites a Python repr into the artifact body with no error and no warning. Returning nothing is the one case that fails loudly.
Audit a repository without changing your working tree
An audit is most useful on a tree holding uncommitted work, which is exactly the tree where a write cannot be undone. Run an audit with auditing-repo and it now reads without writing: a CRITICAL conduct rule constrains the auditor rather than the repository, and Phase 0 of the audit procedure states it before the first check runs (#109).
- Get your uncommitted work back unchanged. In a reported failure, an audit had stashed the tree, run a generator with
--check, popped the stash, and then rungit checkout -- objects/, destroying another agent's regenerated files while reporting its findings correctly and never mentioning that it had modified anything. The rule names cleanup itself as the hazard, including cleanup that undoes the auditor's own side effect. - Read the audit's own account of what it did. The report format now carries the tree's condition at audit time and whether the audit modified it, and an honest "check not performed" finding is available for a check that cannot be run without a write — the compliance checks read the list of commands the audit ran rather than its prose, so disclosing what you did not run counts as compliance.
- Compare against committed state with read-only git:
git show <ref>:<path>,git diff <ref> -- <path>,git cat-file, andgit ls-treeanswer every "what does this look like as committed?" question without touching the tree. - See where a bare-string
kindcould be a typed protocol class. A new LOW-severity rule at step 7 of the cost-to-fix ladder flags non-trivial schema-coupled Python that passesclient.createorclient.getakind="Foo"string, or hand-builds dict payloads, when a generated protocol class exists for that kind. Generate the classes withinfrahubctl protocols, and a schema change becomes a type error on the exact line instead of a runtime failure in the pipeline. The rule carries its own limits: protocols type attributes, not relationship peers, and trivial one-off scripts are not flagged. It sits at the far end of the same cost-to-fix ladder whose first step is Reuse a schema the marketplace already publishes (#79).
Bug fixes
- Every
infrahubctlcommand the skills print is now a real one. A sweep found 11 invalid invocations —schema validate,check run <name>,transform run <name>,generator run <name>,generator list, andimport load— of which only two had been reported. The offline gate isinfrahubctl schema format --check, now documented in managing-schemas (#74), and the server-side check isinfrahubctl schema check.check,generator, andtransformtake their target as a positional argument, socheck run mycheckhad looked for a check literally namedrun, and there is noimportorexportcommand group, since the LDJSON tools are the top-levelloadanddump. Every form was verified againstinfrahub-sdk1.23.1, and a continuous-integration check now gates every invocation the repository prints, including the ones in graders and eval rubrics (#110). infrahubctl rendercannot serve a Python transform, and the message it returns for one — "Unable to find <name> in repository config file" — reads like an unregistered transform rather than the wrong command, so a reader concludes the dry-run gate is unavailable and skips it.renderresolves Jinja2 transforms andtransformresolves Python ones; the rule now gives both and says which serves which section, plus the two details that each cost a failed run: a required query variable must be typed manually when run locally, because the artifact definition binds it from the target in the pipeline, and neither command reaches a check or a generator (#110).infrahubctl inforeports a green status with no token, because the user lookup is skipped entirely when neither a token nor a username is set while the server information call succeeds anonymously. A green result therefore does not prove write authorization. The connectivity rule now carries that three-way asymmetry and gives a create-and-delete branch write probe rather than a branch listing, which is a read (#110).- The
infrahubctl --versionflag does not exist and errors with "No such option".infrahubctl infoprintsSDK VersionandInfrahub Versiontogether. Three places were wrong, including the shared connectivity rule every skill inherits, which showed output labels that appear nowhere in the real output — so grepping for them found nothing (#119). - Retrofitting or changing a relationship
identifieron a relationship that already exists in an instance produces'not_supported': <Kind> <rel> Nonefrominfrahubctl schema check, and nothing explained it. It is a schema-update immutability error, not an invalidkind/cardinalitypairing:identifier,direction,branch, andhierarchicalare the relationship fields that cannot be updated, whilekind,cardinality, andoptionalcan. Anykind/cardinalitypairing is valid given a shared identifier and a fresh kind. Both the error and its fix are now in the relationship rules and the common-errors lookup table (#75).
Minor changes
Developer experience
AGENTS.mdlinks thedev/guides instead of@-importing them, so each guide loads only when a task needs it; always-loaded project memory drops from about 50 KB to about 4.5 KB per session (#77).eval.yamlloads inskillgradeagain. Seven grader-lesscsv-import-*tasks had failed configuration validation before any task could run, which blocked all of them for every skill (#126).
Upgrade notes
Vendored copies of the skills
If: you copied these skills into a repository rather than installing the plugin.
Then: refresh the copy. The invalid command forms corrected here — among them infrahubctl protocols generate --async, which is really infrahubctl protocols — had already propagated into vendored copies, so a stale copy keeps printing commands that do not exist.
Notes: no action is needed if you install the plugin.
Full changelog
Added
infrahub-converting-netbox-device-typesskill, with a bundled converter, three mapping profiles, and a module-port generator (#80).infrahub-teaching-conceptsskill: twelve tutor rules, a dependency-ordered concept map, and a lesson protocol (#118).infrahub-analyzing-diagnosticsskill for diagnostic-bundle triage (#76).infrahub-reporting-skill-gapsskill, filing throughinfrahub-reporting-issues(#83).- Marketplace visibility across the schema, objects, generators, and audit skills, plus the
yagni-reuse-existing-marketplace-schemaaudit rule (#69). yagni-untyped-python-vs-generated-protocolsaudit rule and generated-protocol adoption guidance (#79).watchdependency declarations ininfrahub-common, managing-transforms, and managing-generators (#89).- Read-only audit conduct rule and Phase 0 of the audit procedure (#109).
uniqueness_constraintssemantics, including inheritance scope, the null-collision case, and the relationship preconditions (#111, #85).- Generic inheritance rules for
order_byscope,Dropdownoverrides, and relationship peer kinds (#112). - Schema reuse rules and the generic-membership interface rule (#113).
- Cardinality consequences across the schema and GraphQL query rules (#114).
image/svg+xmlartifact example and the artifact serialization matrix (#115).- GraphQL error-surface and shared-module rules for managing-checks (#116).
- Relationship-traversal validation rule for managing-checks (#81).
- Path traversal, group membership, and shared-object ownership rules for managing-generators (#117).
infrahubctl schema formatdocumented in managing-schemas (#74).- Infrahub SDK and Infrahub versions in the skill-gap report header (#119).
python-lintjob with a pinned ruff rule set (#128).- Python test suite in continuous integration (#80).
Changed
AGENTS.mdlinks thedev/guides instead of@-importing them (#77).- Review lessons from pull requests #85 through #117 routed into the
dev/guides (#120). - Evidence discovery in reporting-skill-gaps made assistant-agnostic, and the docs-site listing extended to the Issue Reporter and Skill Gap Reporter (#119).
Fixed
- 11 invalid
infrahubctlinvocations, with a continuous-integration gate on every printed invocation (#110). infrahubctl --versionreferences replaced withinfrahubctl info(#119).- Shared-identifier relationship rules and the
not_supportedschema-check error explained (#75). - Seven grader-less eval tasks that stopped
skillgradeloadingeval.yaml(#126). - 14 ruff findings on
main(#128). - A fenced-block marker in an eval prompt that desynchronized the CLI invocation sweep (#130).
uv.locksynced to the project version, removing a stray diff from unrelated pull requests (#121).- direnv files ignored (#82).
Skills included
- infrahub-managing-schemas
- infrahub-managing-objects
- infrahub-managing-checks
- infrahub-managing-generators
- infrahub-managing-transforms
- infrahub-managing-menus
- infrahub-auditing-repo
- infrahub-analyzing-data
- infrahub-reporting-issues
- infrahub-reporting-skill-gaps
- infrahub-collecting-diagnostics
- infrahub-analyzing-diagnostics
- infrahub-importing-data
- infrahub-teaching-concepts
- infrahub-converting-netbox-device-types