⚠️ This beta is intended for testing purposes only and should not be used in production environments.
⚠️ This beta release carries several breaking changes. Read the Breaking changes section before upgrading - in particular the new structured GraphQL error codes, the reserved node_metadata name, restricted inheritance on internal Core generics, and the BuiltinIPPrefix.resource_pool change.
⚠️ Single Sign-On accounts. v1.9.0 began capturing additional identity information against accounts created from OAuth2/OIDC providers, and warned that a future release would consume it. This is that release. v1.10 keys SSO accounts on the provider-issued identity (sub) rather than the display name. A transitional setting, INFRAHUB_SECURITY_SSO_ACCOUNT_NAME_FALLBACK (enabled by default), still lets an SSO login adopt a pre-existing account by matching its display name, so upgrades remain smooth. Make sure every SSO user has logged in at least once, then disable the fallback as a hardening step - see Reworked SSO account identity below.
We're excited to announce v1.10.0b0, the first beta of Infrahub 1.10.0.
The headline addition is graph path traversal - a new way to ask the question Infrahub's graph was built to answer: how is this connected to that? A new GraphQL API and a visual topology explorer let you trace every path between two objects, or find everything reachable from a single node for impact analysis. Infrahub Enterprise also gains native LDAP authentication, completing the external-authentication story for organizations without an OIDC/OAuth2 identity provider.
Around those, the release is centred on three themes: authentication & identity - LDAP for Enterprise, auto-creation of account groups from identity-provider claims, and a reworked SSO account-identity model; performance & reliability - branch merges executed at the database level, precise artifact regeneration that only rebuilds what a commit actually changed, and a lighter computed-attribute and task pipeline; and API ergonomics - a structured, machine-readable GraphQL error catalogue and richer order_by controls for schema designers.
Main changes
Graph path traversal and topology explorer
Infrahub's data lives in a graph, and 1.10 finally exposes the graph's most natural capability: traversal. Two new top-level GraphQL queries let you walk relationships across the entire model - not just physical cabling, but any relationship between objects.
InfrahubPathTraversalfinds the paths between two specific objects. Given asource_idand adestination_id, it returns the connecting paths (shortest first) as an ordered list of hops, each hop naming the node and the relationship traversed. Bound the search withmax_depthandmax_paths, and narrow it withkind_filter,relationship_filter,excluded_kinds, andexcluded_namespaces.InfrahubReachableNodesanswers "what depends on this?" Given asource_idand a list oftarget_kinds, it returns every reachable object of those kinds together with the shortest path to each - ideal for blast-radius and impact analysis ("if this device goes offline, what's affected?").
Traversal is branch- and time-aware, read-only, and permission-safe: any path that crosses an object the user cannot read is dropped entirely rather than leaked. A set of internal namespaces (Core, Internal, Builtin, Lineage, Profile, Template) is always excluded so results stay focused on your data; excluded_namespaces only adds to that set.
In the UI, a new Topology Explorer renders results as an interactive graph built on React Flow with automatic layout. You can switch between path mode and dependency mode, filter by kind and namespace, highlight an individual path, and step between paths from the keyboard. Object detail pages gain a Trace from this object action to launch the explorer pre-seeded with that node. Because results come back over GraphQL, the same traversal is available for AI assistants over the MCP server, allowing agents to get a full contextual understanding of data in the intended infrastructure graph.
LDAP authentication (Enterprise)
Infrahub Enterprise now supports native LDAP authentication against Active Directory, OpenLDAP, and other RFC 4510-compliant directories. This gives organizations that have no OIDC/OAuth2 identity provider a first-class external-authentication path that coexists with local accounts and SSO - all three can be active at once and converge on the same session, account, and group handling. On first successful login an Infrahub account is provisioned automatically, matching existing SSO behaviour.
Configuration is via INFRAHUB_LDAP_* environment variables and is validated at startup. You can point Infrahub at one or more servers (the first is primary, the rest act as failover), choose bind-then-search with a service account, select the username_attribute appropriate to your directory (for example sAMAccountName on AD or uid on OpenLDAP), and secure the connection with LDAPS or StartTLS. Optional group mapping (group_enabled) matches LDAP group membership - including nested groups - against existing Infrahub CoreAccountGroup names, granting permissions through the usual account-group → role → permission chain. Combined with auto-created groups (below), group membership can be provisioned on first login with no manual setup.
This feature is available on Infrahub Enterprise only.
Auto-create account groups from your identity provider
Infrahub can now create account groups automatically from identity-provider claims on login, so you no longer have to pre-create a CoreAccountGroup for every group your IdP (or LDAP directory) reports. The feature is opt-in and off by default.
Activate it by setting a claim filter:
security.auto_create_groups_filter(INFRAHUB_SECURITY_AUTO_CREATE_GROUPS_FILTER) - a regular expression, or an ordered list of them, matched against each incoming group claim. The first match wins. A named capture group(?P<name>...)becomes the local group name (e.g.^LDAP/group/(?P<name>.+)$mapsLDAP/group/network-engtonetwork-eng); with no capture the full claim is used. Non-matching claims are dropped, so unrelated IdP groups never pollute Infrahub. Setting this filter is what activates the feature; leaving it empty keeps auto-creation off.security.auto_create_groups_max_per_login- caps the number of new groups created in a single login (default50). Reuse of existing groups is never capped. If the cap is hit, surplus claims are dropped, a warning event is emitted, and the login still completes.
Auto-created groups start with no roles or permissions and run on every external login (OIDC, OAuth2, and native LDAP). If at least one claim matches, it takes precedence over the existing security.sso_user_default_group fallback; if none match, that fallback still applies.
Reworked SSO account identity
Single Sign-On accounts are now keyed on the stable, provider-issued subject identifier (sub) together with the provider and protocol, stored on a dedicated CoreExternalIdentity node, rather than on the user's display name. This removes a class of problems where two users sharing a display name could collide, or where a changed display name produced a duplicate account on the next login. The human-readable name now lives on the account's label attribute and is refreshed when it goes stale.
To keep upgrades smooth, the transitional setting INFRAHUB_SECURITY_SSO_ACCOUNT_NAME_FALLBACK (security.sso_account_name_fallback, enabled by default) allows an SSO login that has no linked identity yet to adopt a pre-existing account that matches by display name. This setting is deprecated and will be removed in a future release. The recommended sequence is: upgrade, have every SSO user log in at least once so their CoreExternalIdentity is created, then disable the fallback. Once it is disabled, any SSO account that never re-logged in will be treated as new and a duplicate account will be created - so complete the re-login step first.
Structured GraphQL error catalogue
GraphQL errors are now structured and machine-readable. Every entry in a GraphQL errors[] array carries an extensions block with:
extensions.code- a stable string identifier such as"NODE_NOT_FOUND"or"AUTHENTICATION_REQUIRED".extensions.data- a typed payload specific to that code (may be empty).extensions.http_status- the integer HTTP status.
You can now branch on a stable code instead of parsing the human-readable message, which remains present but is explicitly not part of the stable contract. Multi-field validation failures emit one error per offending field, each with its own code, data, and a path pointing at the exact input - enabling single-pass form validation. The catalogue ships codes including NODE_NOT_FOUND, AUTHENTICATION_REQUIRED, TOKEN_EXPIRED, PERMISSION_DENIED, ATTRIBUTE_REQUIRED, ATTRIBUTE_INVALID_TYPE, ATTRIBUTE_CONSTRAINT_VIOLATION, BRANCH_NOT_FOUND, and SCHEMA_NOT_FOUND, with UNDEFINED_ERROR as a fallback for any uncatalogued exception.
This is a breaking change for consumers that previously read extensions.code as an integer - see Breaking changes for migration guidance. REST (/api/...) responses are unchanged.
Metadata fields and explicit sort direction can now be used to order data.
Schema order_by entries gain two capabilities, letting designers set smarter defaults without passing an ordering argument on every query or view:
- Order by object-level metadata:
node_metadata__created_atandnode_metadata__updated_atsort by the timestamps Infrahub tracks on every node. - Add an explicit direction: any entry may end in
__ascor__desc(e.g.name__value__desc,node_metadata__created_at__desc). Without a suffix, ascending is assumed, so existingorder_bydefinitions keep their current behaviour.
The new grammar is honored consistently across top-level listings, relationship-peer listings, and hierarchy (ancestors/descendants) listings, and a node UUID tiebreaker is always appended so ordering - and pagination - is stable across all paths.
nodes:
- name: Note
namespace: Documentation
order_by:
- node_metadata__created_at__desc # newest first by creation time
attributes:
- name: title
kind: TextThe same grammar is available at query time through the GraphQL order argument, which accepts an order_by: [String!] list at the root level, on many-relationship fields, and on hierarchical relationships:
query {
DocumentationNote(order: { order_by: ["node_metadata__created_at__desc"] }) {
edges { node { title { value } } }
}
}Two behaviour changes to note: a query-time order argument now fully replaces the schema-level order_by default instead of layering on top of it, and node_metadata is now a reserved attribute/relationship name (see Breaking changes).
Object templates can assign groups to their instances
Object Templates now expose two new relationships, member_of_groups_for_instances and subscriber_of_groups_for_instances. Groups assigned through these fields are propagated to every object created from the template, mirroring the resource-pool pattern. The existing member_of_groups and subscriber_of_groups continue to apply to the template object itself only.
# On a template: every instance created from it joins "production-devices"
member_of_groups_for_instances:
- production-devicesSmarter artifact regeneration on repository changes
When a Proposed Change includes commits to a linked repository, Infrahub now regenerates only the artifacts whose transform source, GraphQL query, or artifact definition was actually affected by the change - instead of regenerating every artifact in the repository. A typo fixed in an unrelated README no longer triggers a regeneration sweep across thousands of nodes. Each regeneration decision is recorded in the Proposed Change's task log, naming the file, query, or definition field that triggered it.
Detection automatically follows Jinja2 transitive includes ({% include %}, {% import %}, {% extends %}) and a Python transform's package directory. For dependencies Infrahub cannot detect automatically - templates pulled in dynamically, or helper modules imported at runtime - you can declare extra files with a new optional watch: key on jinja2_transforms and python_transforms entries in .infrahub.yml. It is recommended to track dependencies explicitly for both Python and Jinja2 transforms.
jinja2_transforms:
- name: device_config
template_path: "templates/device.j2"
watch:
files:
- "templates/partials/" # directory entries are recursive
- "templates/macros/common.j2"The rollout is self-healing: transforms imported before the upgrade keep working unchanged and conservatively regenerate on any file change in their repository until their next import, after which they adopt the precise behaviour automatically - no manual migration. Read-only repositories now participate fully even when sync_with_git is False.
Faster, more reliable branch merges
The merge of a branch or Proposed Change has been reworked along three axes:
- Performance - the merge logic moved from in-memory processing down to the database level. Previously the diff was loaded and serialized one piece at a time into Cypher; large merges that could take tens of minutes are now substantially faster and more robust.
- Stability - removing the step-by-step in-memory serialization eliminates a class of failure modes that could leave an instance in a hard-to-recover state. A specific correctness fix: the merge can now correctly delete an object whose kind or inheritance was changed on the default branch after the merging branch forked.
- Failure handling -
infrahub upgradeno longer overwrites the status of branches in a terminal state (MERGED,DELETING), so they no longer reappear asNEED_UPGRADE_REBASEafter an upgrade.
Generator dispatch within a Proposed Change is also more precise: a generator instance is now only re-run when the diff touches a field its GraphQL query actually reads, rather than for every instance that merely shares a relationship target with a changed node.
Reworked database migration experience
Database migrations - run on upgrade via infrahub db migrate - are now far more transparent and controllable. The command gains a rich-based, staged console output plus several new flags: --check (report state without applying), --plan (show the execution plan and the Cypher query names without running anything), --verbose (detailed per-migration output), and --migration-number (apply a single migration). Two new commands, infrahub db showmigrations and infrahub db showmigration <n>, list all migrations with their applied/pending status and describe an individual migration. You can see exactly which step is running without being flooded by default.
Infrahub MCP Server - Phase 2
The Infrahub MCP server - a separate component that lets AI assistants and LLMs (Claude Desktop/Code, Cursor, custom agents) talk to Infrahub over the Model Context Protocol - graduates to a deployable, production-ready service. Phase 2 adds:
- Deployment - a multi-arch container image published to
registry.opsmill.io/opsmill/infrahub-mcp-server, shipped as an optional service in both Docker Compose and theinfrahub-helmchart (e.g.mcp-server.enabled: true). - Authentication - pass-through auth so each agent connects under the user's own Infrahub API token, preserving the permission model and audit trail rather than using a shared service account.
- Write controls - a read-only mode (
INFRAHUB_MCP_READ_ONLY=true) that stops write tools from being registered, plus branch targeting so mutations land on an isolated per-session or user-specified branch instead of main, aligning with the Proposed Change workflow. - Documentation - deployment, authentication, and configuration guides plus agent-integration examples.
The server lives in its own infrahub-mcp-server repository.
UI: path-based tab navigation and unified data fetching
Two structural improvements make the web UI faster and more predictable:
- Path-based tab navigation - tabs on the Profile, Branch details, Proposed change, and Object details pages now live in the URL path (
/profile/tokens,/branches/foo/data,/proposed-changes/abc/checks,/objects/CoreTag/abc/members) instead of a?tab=query parameter. Each tab is deep-linkable and bookmarkable, browser back/forward moves between tabs as distinct history entries, and each tab's content is lazy-loaded. Old?tab=bookmarks still load the page but land on its default tab - re-bookmark from the new URL. - Unified data-fetching layer - the UI now uses a single library (TanStack Query) for all GraphQL access, where it previously mixed two with caching disabled. Lists, detail pages, and forms share one caching layer, so returning to a page you just visited reuses the data instead of refetching, and the JavaScript bundle is smaller. No user action is required and there are no URL changes.
The account token list page also received a design refresh for a cleaner, more readable layout.
Breaking changes
Read this section before upgrading.
Structured GraphQL error codes
GraphQL error responses now carry a stable string code in extensions.code (e.g. "NODE_NOT_FOUND", "AUTHENTICATION_REQUIRED"), a typed extensions.data payload, and a new integer extensions.http_status. Previously extensions.code was an integer mirroring the HTTP status (most visible on the /graphql authentication short-circuit path).
// Before
{ "errors": [ { "message": "Authentication required.",
"extensions": { "code": 401 } } ] }
// After
{ "errors": [ { "message": "Authentication required.",
"extensions": { "code": "AUTHENTICATION_REQUIRED", "http_status": 401, "data": {} } } ] }Consumers that read the integer code (for example if (extensions.code === 401)) must migrate. Move numeric checks to extensions.http_status, or - preferably - switch on the stable string code (extensions.code === "AUTHENTICATION_REQUIRED"). This affects any GraphQL client, including external integrations. REST (/api/...) responses are unchanged.
node_metadata is now a reserved name
With the new metadata-aware order_by grammar, node_metadata becomes a reserved attribute and relationship name. A schema that literally uses node_metadata as an attribute or relationship name will fail to load on upgrade and must be renamed. Malformed order_by entries (an unsupported metadata field, a bad direction token such as __descending, or duplicate/conflicting entries) are now rejected at schema-load time with an actionable error naming the offending node and entry.
Note also the behaviour change above: a query-time order argument now fully replaces the schema-level order_by default rather than layering on top of it.
Restricted inheritance on internal Core generics
Continuing the namespace-restriction work started in 1.9 (which covered CoreGenericRepository and CoreWebhook), several additional built-in Core generics that have special internal handling now restrict inheritance to the Core namespace. Any user-defined node schema that inherits from one of the generics below must be removed - and its data deleted - before upgrading. Infrahub will refuse to load a violating schema and the upgrade will not complete. The newly restricted generics are:
CoreCredentialCoreGenericAccountCoreResourcePoolCoreIPPoolCoreTransformationCoreBasePermissionCoreMenuCoreCommentCoreThreadCoreValidatorCoreKeyValueCoreTriggerRuleCoreActionCoreNodeTriggerMatch
BuiltinIPPrefix.resource_pool now returns the CoreIPPool generic
A new CoreIPPool generic groups CoreIPPrefixPool and CoreIPAddressPool, and BuiltinIPPrefix.resource_pool now points at it. This fixes the IP-prefix detail "Resource Pool" tab so it lists both pool types that reference the prefix; a database migration consolidates the relationships on upgrade. Because resource_pool now returns the abstract generic, GraphQL queries that selected concrete-pool fields directly under resource_pool must use inline fragments. Fields on the CoreResourcePool generic (name, description) still select directly.
# Before
query {
BuiltinIPPrefix { edges { node { resource_pool { edges { node {
name { value } default_address_type { value }
} } } } } }
}
# After
query {
BuiltinIPPrefix { edges { node { resource_pool { edges { node {
name { value }
... on CoreIPAddressPool { default_address_type { value } }
... on CoreIPPrefixPool { default_prefix_type { value } }
} } } } } }
}Single Sign-On account identity
SSO accounts are now keyed on the provider-issued sub rather than the display name (see Reworked SSO account identity). The transitional INFRAHUB_SECURITY_SSO_ACCOUNT_NAME_FALLBACK setting (default enabled, deprecated) preserves the old display-name matching during the transition. Ensure all SSO users log in once after upgrading before you disable it, otherwise accounts that have not re-logged in will be treated as new and duplicated.
Full changelog
Security
- Bumped transitive docs dependencies to address Dependabot advisories:
dompurify>= 3.4.0,follow-redirects>= 1.16.0,lodashandlodash-es>= 4.18.0,postcss>= 8.5.10, anduuid(v11) >= 11.1.1.
Added
-
Object Templates now expose
member_of_groups_for_instancesandsubscriber_of_groups_for_instancesrelationships. Groups assigned through these fields are propagated to every object created from the template, mirroring the resource-pool pattern. The existingmember_of_groupsandsubscriber_of_groupson a template continue to apply to the template itself only. (#9094) -
Added graph path traversal feature with visual topology explorer. Users can discover paths between any two nodes, find dependencies from a source node, filter by kind and namespace, and explore the graph interactively with React Flow visualization.
-
Infrahub can now auto-create account groups from identity-provider claims on SSO login. Opt in by configuring a claim filter under
security.auto_create_groups_filter, with an optional per-login cap. -
Schema
order_byentries can now reference object-level metadata and carry an explicit direction suffix:node_metadata__created_atandnode_metadata__updated_atorder by object-level timestamps.- Any entry may end with
__ascor__desc(e.g.name__value__desc,node_metadata__created_at__desc). Without a suffix, ascending order is assumed. - The new grammar is honored consistently across top-level object listings, relationship-peer listings, and hierarchy listings. A UUID tiebreaker is always appended so ordering is stable across paths.
Behavior change: a query-time
orderargument now fully replaces the schema-levelorder_bydefault instead of being layered on top of it.GraphQL
orderargument accepts anorder_by: [String!]list using the same grammar as the schema'sorder_byfield. This works at the root level, on many-relationship fields, and on hierarchical (ancestors/descendants) relationships.order_bycannot be combined with the legacynode_metadataform in the same argument.Breaking change:
node_metadatais now a reserved attribute and relationship name. Schemas that literally usenode_metadataas an attribute or relationship name will fail to load and must rename the offending element.
Changed
-
Breaking: GraphQL error responses now carry a stable string code in
extensions.code(e.g."NODE_NOT_FOUND","AUTHENTICATION_REQUIRED") and a typedextensions.datapayload, plus a new integerextensions.http_status. Previouslyextensions.codewas an integer mirroring the HTTP status. Consumers reading the integer code (most commonly on the/graphqlauth-short-circuit path) must migrate to switching on the string code; numeric checks now readextensions.http_status. REST/api/...responses are unchanged. -
HFID attribute values are now indexed in Neo4j for faster lookups. A migration normalizes existing HFID values to consistent all-string format and adds database indexes. The HFID lookup query has been simplified to match directly on the stored value instead of reconstructing per-field filters.
-
Improve merge performance by moving the logic to the database level
-
Improved design of the account token list page
-
Prefect task read queries optimized to fetch only required fields.
client.all()andclient.filters()calls replaced with targetedexecute_graphql()queries indisplay_labels,hfid,computed_attribute,git, andgeneratorstasks, significantly reducing data transfer per workflow execution. -
Refined graph path traversal API: renamed
InfrahubDependenciestoInfrahubReachableNodes, renamednode_filterinput tokind_filter, added generic-kind support in filters, and hardened default-branch edge filtering. -
Several built-in
Coregeneric schemas that have special handling in Infrahub now restrict inheritance to theCorenamespace via therestricted_namespacesfield. This prevents user-defined schemas from inheriting from generics whose code paths assume a specific internal structure.Breaking change. Any user-defined node schema that inherits from one of the generics listed below must be removed (and its data deleted) before upgrading. Infrahub will refuse to load a schema that violates these restrictions and the upgrade will not complete.
The newly restricted generics are:
CoreCredentialCoreGenericAccountCoreResourcePoolCoreIPPoolCoreTransformationCoreBasePermissionCoreMenuCoreCommentCoreThreadCoreValidatorCoreKeyValueCoreTriggerRuleCoreActionCoreNodeTriggerMatch
-
Tab navigation on detail pages (Profile, Branch details, Proposed change, Object details) now uses URL path segments (
/profile/tokens,/branches/foo/data,/proposed-changes/abc/checks,/objects/CoreTag/abc/members) instead of?tab=query parameters. Browser back/forward navigates between tabs as distinct history entries, and each tab's content is lazy-loaded per route. Bookmarks to old?tab=URLs render the default tab. -
The web UI's data-fetching layer has been unified on TanStack Query. Previously the frontend mixed two libraries (Apollo and TanStack) for talking to the GraphQL API, with caching disabled globally. Lists, detail pages and forms now share a single caching layer, so navigating back to a page you just visited reuses the data instead of refetching it, and the JavaScript bundle is smaller. No user action is required and there are no URL changes from this update.
-
When a proposed change includes commits to a linked repository, Infrahub now regenerates only the artifacts whose transform source, GraphQL query, or artifact definition was actually affected by the change, instead of regenerating every artifact in the repository. Each regeneration decision is recorded in the proposed change's task log, naming the file, query, or definition field that triggered it. Transforms imported before this change keep working unchanged and adopt the precise behavior automatically on their next import; until then they conservatively regenerate on any file change in their repository.
For dependencies Infrahub cannot detect automatically, such as templates pulled in dynamically or helper modules imported at runtime, you can declare extra files with a new optional
watch:key onjinja2_transformsandpython_transformsentries in.infrahub.yml. Changes to a declared file or directory then trigger regeneration of that transform's artifacts.
Fixed
-
Added a new boolean input, allowing users to explicitly submit true, false, or null values. (#4418)
-
Removing an attribute or relationship from a generic schema now also reconciles
uniqueness_constraints,human_friendly_id,order_by, anddefault_filteron inheriting node schemas. Previously a node that inherited a constraint referencing the removed field could end up with an orphaned path, causing aRequested unique constraint not found within nodeexception. (#5735) -
Stop
infrahub upgradefrom overwriting the status of merged or deleting branches. Branches in a terminal state (MERGED,DELETING) are now skipped, so they no longer reappear asNEED_UPGRADE_REBASEafter an upgrade. (#9103) -
Stop object-type conversion of agnostic nodes with aware attributes from re-opening merged or deleting branches. The "needs rebase" status now only applies to non-terminal branches. (#9103)
-
Prevent duplicated Node, Generic, Attribute, and Relationship schemas from being created in the case of a worker's in-memory schema cache being stale while updating schemas. (#9250)
-
Fix a bug in the merge logic that prevented the merge operation from deleting an object that had its kind or inheritance updated on the default branch after the branch being merged forked. (#9283)
-
Within a proposed change, generator instances are now only re-run when the diff touches a field that the generator's GraphQL query actually reads. Previously a generator was dispatched for every instance sharing a relationship target with a changed node, even when the change touched no field the query depended on. (#9378)
-
Fix the "Resource Pool" tab on the IP prefix detail view so that it now lists both
CoreIPPrefixPoolandCoreIPAddressPoolpools that have the prefix as a resource. A newCoreIPPoolgeneric groups the two pool types and theBuiltinIPPrefix.resource_poolrelationship now points at it. A database migration retroactively consolidates the IP pool ↔ IP prefix relationships.Breaking change for GraphQL clients. Because
BuiltinIPPrefix.resource_poolnow returns the abstractCoreIPPoolgeneric (instead of the concreteCoreIPAddressPool), any existing GraphQL queries that selected fields directly underresource_poolwill need to be updated to use inline fragments for fields that are specific toCoreIPAddressPoolorCoreIPPrefixPool. For example:# Before query { BuiltinIPPrefix { edges { node { resource_pool { edges { node { name { value } default_address_type { value } } } } } } } } # After query { BuiltinIPPrefix { edges { node { resource_pool { edges { node { name { value } ... on CoreIPAddressPool { default_address_type { value } } ... on CoreIPPrefixPool { default_prefix_type { value } } } } } } } } }
Fields that exist on the
CoreResourcePoolgeneric (name,description) can still be selected directly without a fragment.
Housekeeping
- Refactored permission system to extract decision logic into a single
PermissionResolverclass.PermissionManagernow delegates resolution to the resolver and the permission report uses the same resolver, ensuring the GraphQL pipeline and UI report always agree.