Skip to content

Merge 'release-1.11' to 'develop' with resolved conflicts. - #10342

Merged
ogenstad merged 30 commits into
developfrom
pog-release-1.11-to-develop
Aug 20, 2026
Merged

Merge 'release-1.11' to 'develop' with resolved conflicts.#10342
ogenstad merged 30 commits into
developfrom
pog-release-1.11-to-develop

Conversation

@ogenstad

@ogenstad ogenstad commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Why

Bring latest changes from release-1.11 to develop.

Replaces #10236

What changed

  • All changes from the release-1.11 branch
  • Trivial merge conflict in backend/infrahub/core/migrations/query/attribute_rename.py

The signature of AttributeRenameQuery.query_init changed **kwargs: dict[str, Any] -> **kwargs: Any, and then two removed methods above this one had to be deleted.

Review in cubic

pa-lem and others added 30 commits August 17, 2026 14:42
…#9958)

* docs(preferences): user-facing docs, changelog, and e2e tests (IFC-2737)

Add the user-facing "Managing preferences" page under Deployment & Management >
User Management & Security (single page, Web + GraphQL tabs): precedence
(user > organisation default > browser), the date-format presets, setting
personal preferences and organisation defaults, and the manage_global_preferences
gate. Register it in the sidebar after "Managing API tokens".

Add the missing changelog fragment for preference-driven date/time rendering
(IFC-2721); the existing fragments only covered setting preferences.

Add Python e2e coverage: personal round-trip + clear-override, global-default
inheritance, and the permission gate. Introduce a shared select_combobox_option
helper for the preference forms.

Facts grounded in the code on this branch (literal UI labels, the "re-select to
clear" reset, the InfrahubSetPreferences/InfrahubEffectivePreferences surface).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(preferences): follow the global-preferences move to the account menu

The global preferences page moved from a profile tab to a standalone
/global-preferences route behind a permission-gated account-menu item;
the docs step and both e2e tests now walk that flow. Also de-flake the
tests that never went green: assert the URL with a regex (a string
pattern resolves against the base URL and the glob never matched) and
open the source tooltip via focus, which React Aria shows immediately,
instead of the delay-prone hover.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(e2e): drop work-item IDs from the preference test docstrings

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(e2e): expand the unauthorized accordion before asserting the permission message

The UnauthorizedScreen renders the detailed message inside a collapsed
Accordion, so the text is not in the DOM until the title is clicked.
Assert the visible title first, expand it, then check the message.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(e2e): deduplicate the success toast between the two preference saves

The second 'Preferences updated' check could match the first save's
still-visible toast, letting the reload race (and cancel) the in-flight
clear mutation. Wait for the first toast to dismiss, and assert the
trigger shows the inherited placeholder before saving the clear.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: preference improvement

* remove known limitation

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Baptiste <girard.baptiste@hotmail.fr>
Co-authored-by: Saltaferis Dimitrios <d.saltaferis@gmail.com>
…mespace/inheritance update (#10277)

* refactor(schema): derive vertex labels from a single definition

Node.get_labels() re-implemented the label rules inline rather than using the
schema's own get_labels(), and the two had drifted.

TemplateSchema.get_labels() appended CoreObjectTemplate on top of an
inherit_from that already contained it, so it returned that label twice, and it
claimed the label for component subtemplates, which are built on
CoreObjectComponentTemplate instead. ProfileSchema.get_labels() had the same
defect with CoreProfile. Neither append could ever add a missing label, since
the generators always place the base kind in inherit_from.

Both methods were unreachable: the labels written at creation came only from the
inline copy in Node, so the drift went unnoticed. Dropping the appends, giving
BaseNodeSchema the bare-kind default and delegating from Node leaves the labels
written for every schema type unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(migrations): relabel generated Profile/Template vertices on a kind update

The schema diff reports only nodes and generics, so the Profile{Kind} and
Template{Kind} schemas generated from a node never produced a migration of their
own. A kind update relabelled the node's own vertices and left theirs untouched:
a template kept a stale label set when its kind gained or lost a generic, and a
rename left both generated populations answering to kinds that no longer resolved.

schema_apply_migrations already holds the previous and the new SchemaBranch, so it
now resolves the generated schemas for the kind being migrated and passes them to
the migration, which folds them into a single NodeDuplicateQuery run. Reading the
real generated schemas keeps the label sets sourced from the generators rather
than restated, which matters because a template's labels depend on schema-wide
state a single node schema cannot supply.

Matching moves from a conjunction of the previous label set to a disjunction of
the kinds being migrated, with the target looked up per vertex by kind. A vertex
whose label set has already drifted is therefore repaired rather than skipped, and
one that merely carries a matched label without being of a mapped kind is ignored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* add changelog

* Correct capitalization in changelog entry

* fix metadata test

it was actually broken since creation and the latest changes to
NodeDuplicateQuery just revealed it

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The medium and medium-data presets both listed a 24G Neo4j heap against
a 32 GB total memory budget, leaving no room for the 4G page cache and
the rest of the stack. Bring both rows down to 16G.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Merge stable into release-1.11
…10254)

* fix: close branch-owned edges when renaming an attribute on a branch

The rename ended the old attribute by creating a deleted edge beside it. That is the
only option for an edge owned by the default branch, which a branch cannot modify, but
it was applied to every edge — so a node created on the branch kept the old attribute
active and deleted at the same time.

Rebasing then re-stamped both edges to the rebase time, leaving the resolution of "the
latest edge for this attribute" a tie between them. The rename dispatched again after
the rebase could therefore resolve the still-active edge, rename a second time, and
leave the node with two copies of the renamed attribute.

Edges the branch owns are now closed with a `to` time, the way the default branch
already handled them, and only edges owned by another branch are shadowed.

The per-edge-type query rendering is gone with it: direction now comes from the stored
edge rather than from the schema's declared direction, so the ten generated UNION
branches collapse into four subqueries keyed on direction, and the node match uses a
label alternation instead of a three-way UNION.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* rewrite changelog

* fix: keep the rename node-selection overridable and cover the rerun

Inlining the node match into query_init made Migration012RenameTypeAttributeData's
render_match() override dead code, dropping the guard that skips nodes already holding
the new attribute name. The match moves back behind render_match() so a subclass can
still narrow it.

The regression test now re-executes the migration and asserts nothing further is
created, matching its sibling tests. That is the pass the rebase makes in practice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add a "Format schema files" section to the Create and load schema page:
default key-ordering behaviour, --diff / --check, and the opt-in
--strip-defaults / --sort-by-order-weight / --backfill-order-weight flags.

DO NOT MERGE until an infrahub-sdk release that ships `infrahubctl schema
format` is published (feature merged in opsmill/infrahub-sdk-python#1189).
Merging earlier would document a command absent from the released CLI.

Co-authored-by: Alex Gittings <agitting96@gmail.com>
docs: rework IPAM documentation
`infrahub tasks flush stale-runs` only reset flow runs stuck in RUNNING, so
runs picked up by a worker that died before the flow started stayed PENDING
forever. Prefect's start_time filter falls back to expected_start_time, so
never-started runs are aged correctly by the existing cutoff.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Playwright's `name` option substring-matches by default, and the branch
selector renders the current branch name on every page. A base36 random
suffix can therefore spell a word the suites locate by, making that
locator match two elements and killing the test with a strict-mode
violation.

That is what took down object-relationships.spec.ts on #10287: the branch
was object-relationshipsaveyj8q5g6r, where the prefix's trailing "s" and a
suffix starting "ave" spell "save", so getByRole("button", { name: "Save" })
matched both the Save button and the branch selector. It presents as an
unrelated test failing for no reason and passing on re-run, which is why it
kept being written off as noise.

Switch the TypeScript generator to hex, which the pytest helper already
used. Measured over 2M generations against the names the suites locate by:
base36 produces 168 collisions (about 1 in 12k), hex produces 0. It is not
only "save" -- the sample also threw up branchesmainzvsrjh8,
...u6vl6xpathb and ...t6nname02n, colliding with "main", "path" and "name".

None of the 436 distinct locator names across both suites is spellable in
hex, so this closes the class rather than the one instance. Both helpers
now carry the constraint so a wider alphabet does not creep back in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit 4cf2b30)
The fix rests on the branch suffix staying hexadecimal, but that was
guarded only by the comments added alongside it. Prose already failed once
here: these two helpers silently diverged despite the Python one
documenting itself as a port of the TypeScript one, which is how base36
survived long enough to spell "save" in a branch name.

Assert the suffix matches [0-9a-f]{12} on both sides. Verified by
regression, not just by passing: reverting the TypeScript helper to
Math.random().toString(36) fails both of its cases, and perturbing the
Python helper's output fails its.

The Python test needs a shard marker because CI selects with
-m shard_<name> and an unmarked test is deselected in every shard. It also
inherits the suite's compose stack, since pytest-base-url's autouse
_verify_url pulls in base_url; that costs nothing in CI where the stack is
already running, and there is nowhere else the helper is importable from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit ecb56d0)
Bump the prefect-redis pin from 0.2.12 to the latest release, 0.2.14.
Infrahub only consumes it through the Prefect server env vars
(PREFECT_MESSAGING_BROKER/CACHE, PREFECT_SERVER_EVENTS_CAUSAL_ORDERING,
PREFECT_SERVER_CONCURRENCY_LEASE_STORAGE), so the change is confined to
the Prefect server side.

- pyproject.toml: prefect-redis==0.2.12 -> ==0.2.14
- uv.lock: regenerated with uv 0.11.8 (CI's version); prefect 3.7.5 and
  redis 6.0.0 unchanged, no other package moved except a pendulum marker
  refresh that was already stale in the lock

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…igration (#10298)

NodeRelationshipRemoveMigration overrides SchemaMigration.execute to open its
own transaction rather than delegating to the base, which discarded the
retry_db_transaction decorator applied to the base method. A transient error
inside that transaction failed the migration instead of being replayed.

Document the rule where migrations are authored, since an override silently
dropping an inherited decorator is easy to repeat.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stable merge collided on `infrahub tasks flush stale-runs`: release-1.11
moved the purge behind `FlowRunRetention`, while stable widened the state
filter to include PENDING via `STALE_FLOW_RUN_STATES`. Keep both — stable's
call site alone would not import, since release-1.11 dropped `PrefectTask`.

`FlowRunRetention.purge` filters on the same `start_time before cutoff`, so
never-started runs still age off Prefect's `expected_start_time` fallback as
the original fix intended.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adjacency conflict in `requires-dist`: stable bumped prefect-redis to 0.2.14
and release-1.11's prometheus-client pin sits on the following line. Keep
both entries; `uv lock --check` passes against pyproject.toml.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…table

test: generate hex branch names so they cannot spell locator words (backport #10291)
* fix(backend): omit task related nodes with unresolvable kind

A Prefect flow run can be tagged with a related node ID that no longer
resolves to an active node (deleted definition, stale tag). The kind then
stays None and the non-nullable TaskRelatedNode.kind GraphQL field crashed
the resolver, breaking the Tasks page.

Filter such entries out of the related-nodes accessors so the task is
returned without the unrenderable node instead of erroring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(backend): assert resolvable related node survives kind filtering

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ease-1-11

# Conflicts:
#	backend/infrahub/task_manager/event/models.py
`stable` fixed `RelatedNodesInfo.get_related_nodes` in
`task_manager/models.py` (#9664), filtering out related nodes whose kind
never resolved so the non-nullable `TaskRelatedNode.kind` GraphQL field
stops crashing the Tasks page.

`release-1.11` split that flat module into `task_manager/event/` and
`task_manager/flow_run/`. Git's rename detection followed the split to
`event/models.py` and reported the conflict there, but `RelatedNodesInfo`
actually landed in `flow_run/models.py` — every consumer
(`flow_run/enrichment.py`, `reader.py`, `service.py`) imports it from
`.models` there, and `event/models.py` does not reference it at all.

Drop the incoming block from `event/models.py` (restoring it byte-for-byte
to the `release-1.11` version) and apply the two-hunk fix to
`flow_run/models.py`, where the target methods were still identical to the
merge-base so the patch applies with exact context.
`release-1.11` added `TestRelatedNodesInfo` in #9810; `stable` added the
kind filter in #9664. Neither side touched the other's file, so the merge
was clean and five of those tests then failed.

They assert registration mechanics (`add_node`, `add_nodes`, cross-flow
sharing, dedup) but observe through `get_related_nodes`, which now omits
nodes whose kind never resolved. `add_node` leaves `kind=None`, so each
saw an empty list.

Set a kind on the registered nodes, following the idiom already used by
`test_get_related_nodes_as_dict` in this file. The assertions are otherwise
untouched and no longer depend on whether the accessor filters.

Add two tests for the filter's contract, which previously had only
component-level coverage:

- `test_get_related_nodes_omits_unresolvable_kind` pins the fix across all
  three accessors. Verified it fails when the filter is reverted.
- `test_get_unique_related_node_ids_keeps_unresolved_kind` guards the other
  direction: `flow_run/enrichment.py` resolves kinds from that list, so
  filtering it would leave every kind permanently unresolved.
The default test stack carried no com.github.* labels at all, and the
cluster stack only had them on database-core2/core3, so containers left
behind on the runners could not be traced back to the run that spawned
them.

com.github.job falls back to GITHUB_JOB because only the three E2E
workflow jobs export JOB_NAME, while the integration, functional,
docker-integration, benchmark and version-upgrade jobs — the ones that
actually build testcontainers stacks — do not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Merge stable into release-1.11
…10250)

* refactor: consolidate graph-integrity checks behind verify_graph()

Six overlapping graph-integrity checks were split across
infrahub.database.validation and tests.helpers.db_validation, each called
individually, so a suite had to know all six existed and new suites routinely
covered only a subset. They now live in one module behind a single entry point:
verify_graph() raises GraphValidationError listing every violation found rather
than stopping at the first failing check, and collect_graph_violations() returns
them for tests that assert a damaged state. The individual checks are private.

An optional kinds filter narrows every check at its MATCH anchor, so a suite can
scope a run to the kinds it touches; checks anchored on Attribute or Relationship
vertices resolve the label through the Node the vertex hangs off.

The duplicate-attributes check no longer takes a branch. It derives the branch
set from the graph, so one call covers every branch that touched a node instead
of only the one the caller passed, and it prunes to nodes actually holding two
same-named Attribute vertices so a healthy graph does almost no work. It also
groups per vertex rather than per uuid, since a kind update leaves several
vertices sharing one uuid.

The three checks that resolve default-branch visibility now use branched_from
rather than created_at as the fork point, matching the production read path. A
rebased branch does see default-branch edges written after its creation, so
created_at under-included them and the checks could miss a violation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: correct the graph checks surfaced by the consolidated verify_graph()

Running every check at every call site exposed gaps in the checks themselves.

An empty kinds list rendered an empty label expression, so `MATCH (n:)` made every
check fail rather than run unfiltered. It now means no filter, as callers expect.

The orphaned-edge and duplicate-attribute checks selected a default-branch edge as
visible from a branch, then required it to be open, so an edge closed after the branch
forked counted as inactive even though the branch still reads it. Both now allow the
same `to > branched_from` exception the relationship-edge-count check already applied.

The edges-after-delete check compared a branch's edges against the default branch's
delete time. A rebase re-stamps every open edge to the fork point, including the ones
recording the branch's own deletion, so those edges looked like writes to an already
deleted node. An edge on another branch is now only flagged when it was written
strictly after the fork point.

The orphaned-edge check also carried a deletion timestamp through four projections
without ever reading it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor: rewrite the orphaned-edge check around the delete that caused it

The check resolved, per field and peer, the latest second-level edge visible from the
branch that deleted the field, then asked whether it was active. Reading a default
branch edge through a branch's fork point made a value the default branch had since
replaced look live, so an ordinary update after a branch deleted a field was reported
as an orphan.

It now starts from the delete itself. Every branch holding a deleted or closed
first-level edge for a node and field is a candidate; the edges that branch holds
decide whether it deleted the field at all, and the last of them to close is when the
field went away. A delete writes the field's edges at that same moment, so what is
left open, or carries a later time, is what did not follow the delete.

Grouping on the node uuid rather than the vertex replaces the guard for kind updates:
those copy the node vertex, and both copies point at the same field, so an edge left
open on either of them means the field is still in use.

Edges on a branch that forked after the delete are checked too, since that branch
inherits it. A rebase re-stamps the edges it carries to the fork point, so only what
comes after that was written with the delete in view.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: scope an inherited delete to the branches that can see it

A delete only reaches a branch that was cut after it from the branch it happened on.
Branches are cut from the default branch, so a delete on one branch is invisible to
every other, but the check treated any branch forked later as having inherited it and
reported that branch's own writes as orphaned. Two sibling branches, one deleting a
field and the other adding to it, were enough to trigger it.

The violation also named the branch that did the deleting rather than the branch
holding the edge, which pointed at the wrong place to go looking. It now names where
the edge lives, and where the delete came from when they differ.

The duplicate-attribute check now counts a default-branch edge that closed after a
branch forked, which the branch still reads as open. Unlike the orphaned-edge check
this resolves one edge per field vertex, so a value the default branch replaced cannot
be counted twice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix migration

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added type/documentation Improvements or additions to documentation group/backend Issue related to the backend (API Server, Git Agent) group/ci Issue related to the CI pipeline labels Aug 20, 2026
@codspeed-hq

codspeed-hq Bot commented Aug 20, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 13 untouched benchmarks


Comparing pog-release-1.11-to-develop (438afdc) with develop (e8b22b7)

Open in CodSpeed

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 issues found across 127 files

Confidence score: 3/5

  • backend/infrahub/database/validation.py does not exclude deleted or closed relationship versions from the duplicate-path check, so valid attribute or edge updates may be rejected as duplicates — add optional matching and active-edge filters to the Cypher queries.
  • docs/docs/release-notes/infrahub/release-1_11_0.mdx claims its Breaking changes summary is complete while omitting documented changes, and docs/sidebars.ts renames IPAM URLs without updating docs/redirects-pending/ipam.yml; reconcile the release content and redirect tracking.
  • docs/docs/ipam/build-your-ipam-schema.mdx references an undefined or unloaded ServiceGeneric, while dev/guides/backend/creating-migrations.md cites a decorator example with the wrong transaction name; align both examples with the actual definitions and implementation.
  • tests/e2e/preferences/test_global_preferences.py does not verify that the inherited value is correct, and .vale/styles/spelling-exceptions.txt contains a duplicate netmask entry; strengthen the value assertion and remove the duplicate exception.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".vale/styles/spelling-exceptions.txt">

<violation number="1" location=".vale/styles/spelling-exceptions.txt:164">
P3: Custom agent: **Flag AI Slop and Fabricated Changes**

The added `netmask` entry duplicates the existing entry after `Neo4j`. Remove one of the two entries to keep the spelling exception list unique.</violation>
</file>

<file name="backend/infrahub/database/validation.py">

<violation number="1" location="backend/infrahub/database/validation.py:189">
P2: Custom agent: **Check Cypher queries that update attributes/edges for optional matching and active-edge checks**

The duplicate-path check includes deleted or closed relationship versions because it never filters `e` to the active, `to IS NULL` edge. Filter historical edges before grouping so `verify_graph()` reports only duplicate current paths.</violation>
</file>

<file name="dev/guides/backend/creating-migrations.md">

<violation number="1" location="dev/guides/backend/creating-migrations.md:73">
P3: The doc tells the reader to reapply `@retry_db_transaction(name="schema_migration")` and cites `node_relationship_remove.py` as the worked example, but that file's decorator actually uses `name="relationship_remove_schema_migration"` (node_relationship_remove.py:260). Align the doc with the cited example (or drop the explicit name) so the retry metric label matches the code it points to, instead of colliding with the base class's `schema_migration` retry counter.</violation>
</file>

<file name="docs/sidebars.ts">

<violation number="1" location="docs/sidebars.ts:164">
P2: The IPAM sidebar entries are renamed from released page URLs to new ones, but the redirect-tracking document docs/redirects-pending/ipam.yml still lists the old paths (ip-namespaces, building-your-schema, automate-with-resource-manager) as the intended structure. Per the docs AGENTS.md binding rule, renamed/deleted published pages need a redirect entry; update redirects-pending/ipam.yml (and the stale reference in docs/release-notes/infrahub/docs-restructure.mdx) to map the old URLs to the new build-your-ipam-schema / use-namespaces / allocate-ips-and-prefixes pages so the old published URLs keep resolving.</violation>
</file>

<file name="tests/e2e/preferences/test_global_preferences.py">

<violation number="1" location="tests/e2e/preferences/test_global_preferences.py:66">
P3: The test verifies only that an organisation default exists and its source hint appears, not that the correct value is inherited. The tooltip text is `From the organisation default: <value>` and the substring assertion passes for any value, so a regression that inherits the wrong format would not be caught. Assert that the inherited value equals GLOBAL_DATE_FORMAT on the read-only page (and optionally that the ISO browser default is not shown) to give the test its stated purpose.</violation>
</file>

<file name="docs/docs/release-notes/infrahub/release-1_11_0.mdx">

<violation number="1" location="docs/docs/release-notes/infrahub/release-1_11_0.mdx:46">
P2: The "Before upgrading ▸ Breaking changes" summary presents itself as complete ("The full Breaking changes section below explains the impact of each"), and its matching "## Breaking changes" section covers only four items. Several other upgrades-blocking changes exist only in the Full changelog and are absent from both: the GraphQL `extensions.code` string migration (line 488), the restricted_namespaces inheritance restriction which the notes themselves say makes the upgrade fail to complete if violated (line 506), the reserved `node_metadata` name (line 474), and `BuiltinIPPrefix.resource_pool` now returning `CoreIPPool` (line 583). An operator reading the summary would miss required pre-upgrade actions for these. Either add the missing changes to the upfront summary or soften the completeness claim.</violation>
</file>

<file name="docs/docs/ipam/build-your-ipam-schema.mdx">

<violation number="1" location="docs/docs/ipam/build-your-ipam-schema.mdx:159">
P2: The extensions example pairs `IpamIPPrefix` with `peer: ServiceGeneric`, but nothing in this guide defines or loads `ServiceGeneric` — the first example's node is `Service` in the `Customer` namespace (`CustomerService`). Copying the block verbatim will fail schema load. Point the peer at the `CustomerService` node defined in the guide (or instruct the reader to load the Service Catalog schema that provides `ServiceGeneric`).</violation>
</file>

Shadow auto-approve: would not auto-approve because issues were found.

Re-trigger cubic

async def _check_duplicate_paths(db: InfrahubDatabase, kinds: list[str] | None) -> list[GraphViolation]:
"""Verify that no duplicate paths exist at the database level."""
query = """
MATCH path = (p)-[e]->(q)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Custom agent: Check Cypher queries that update attributes/edges for optional matching and active-edge checks

The duplicate-path check includes deleted or closed relationship versions because it never filters e to the active, to IS NULL edge. Filter historical edges before grouping so verify_graph() reports only duplicate current paths.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/infrahub/database/validation.py, line 189:

<comment>The duplicate-path check includes deleted or closed relationship versions because it never filters `e` to the active, `to IS NULL` edge. Filter historical edges before grouping so `verify_graph()` reports only duplicate current paths.</comment>

<file context>
@@ -98,10 +171,322 @@ async def verify_no_edges_added_after_node_delete(db: InfrahubDatabase) -> None:
+async def _check_duplicate_paths(db: InfrahubDatabase, kinds: list[str] | None) -> list[GraphViolation]:
+    """Verify that no duplicate paths exist at the database level."""
+    query = """
+MATCH path = (p)-[e]->(q)
+WHERE %(kind_filter)s
+WITH
</file context>

Comment thread docs/sidebars.ts
'ipam/ip-namespaces',
'ipam/building-your-schema',
'ipam/automate-with-resource-manager',
'ipam/build-your-ipam-schema',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The IPAM sidebar entries are renamed from released page URLs to new ones, but the redirect-tracking document docs/redirects-pending/ipam.yml still lists the old paths (ip-namespaces, building-your-schema, automate-with-resource-manager) as the intended structure. Per the docs AGENTS.md binding rule, renamed/deleted published pages need a redirect entry; update redirects-pending/ipam.yml (and the stale reference in docs/release-notes/infrahub/docs-restructure.mdx) to map the old URLs to the new build-your-ipam-schema / use-namespaces / allocate-ips-and-prefixes pages so the old published URLs keep resolving.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/sidebars.ts, line 164:

<comment>The IPAM sidebar entries are renamed from released page URLs to new ones, but the redirect-tracking document docs/redirects-pending/ipam.yml still lists the old paths (ip-namespaces, building-your-schema, automate-with-resource-manager) as the intended structure. Per the docs AGENTS.md binding rule, renamed/deleted published pages need a redirect entry; update redirects-pending/ipam.yml (and the stale reference in docs/release-notes/infrahub/docs-restructure.mdx) to map the old URLs to the new build-your-ipam-schema / use-namespaces / allocate-ips-and-prefixes pages so the old published URLs keep resolving.</comment>

<file context>
@@ -161,9 +161,11 @@ const sidebars: SidebarsConfig = {
-            'ipam/ip-namespaces',
-            'ipam/building-your-schema',
-            'ipam/automate-with-resource-manager',
+            'ipam/build-your-ipam-schema',
+            'ipam/use-namespaces',
+            'ipam/allocate-ips-and-prefixes',
</file context>

> - **Generated OpenAPI clients:** Code that depends on the previous component schema names in `openapi.json` needs to be updated and regenerated.
> - **API backpressure:** Under sustained overload Infrahub now rejects requests with `429 Too Many Requests` and a `Retry-After` header. The web UI and the Python SDK handle this for you; a custom integration that calls the API directly is expected to handle it itself.
>
> The full Breaking changes section below explains the impact of each and the action to take.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The "Before upgrading ▸ Breaking changes" summary presents itself as complete ("The full Breaking changes section below explains the impact of each"), and its matching "## Breaking changes" section covers only four items. Several other upgrades-blocking changes exist only in the Full changelog and are absent from both: the GraphQL extensions.code string migration (line 488), the restricted_namespaces inheritance restriction which the notes themselves say makes the upgrade fail to complete if violated (line 506), the reserved node_metadata name (line 474), and BuiltinIPPrefix.resource_pool now returning CoreIPPool (line 583). An operator reading the summary would miss required pre-upgrade actions for these. Either add the missing changes to the upfront summary or soften the completeness claim.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/docs/release-notes/infrahub/release-1_11_0.mdx, line 46:

<comment>The "Before upgrading ▸ Breaking changes" summary presents itself as complete ("The full Breaking changes section below explains the impact of each"), and its matching "## Breaking changes" section covers only four items. Several other upgrades-blocking changes exist only in the Full changelog and are absent from both: the GraphQL `extensions.code` string migration (line 488), the restricted_namespaces inheritance restriction which the notes themselves say makes the upgrade fail to complete if violated (line 506), the reserved `node_metadata` name (line 474), and `BuiltinIPPrefix.resource_pool` now returning `CoreIPPool` (line 583). An operator reading the summary would miss required pre-upgrade actions for these. Either add the missing changes to the upfront summary or soften the completeness claim.</comment>

<file context>
@@ -0,0 +1,637 @@
+> - **Generated OpenAPI clients:** Code that depends on the previous component schema names in `openapi.json` needs to be updated and regenerated.
+> - **API backpressure:** Under sustained overload Infrahub now rejects requests with `429 Too Many Requests` and a `Retry-After` header. The web UI and the Python SDK handle this for you; a custom integration that calls the API directly is expected to handle it itself.
+>
+> The full Breaking changes section below explains the impact of each and the action to take.
+
+### Upgrade preparation
</file context>

- kind: IpamIPPrefix
relationships:
- name: service
peer: ServiceGeneric

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The extensions example pairs IpamIPPrefix with peer: ServiceGeneric, but nothing in this guide defines or loads ServiceGeneric — the first example's node is Service in the Customer namespace (CustomerService). Copying the block verbatim will fail schema load. Point the peer at the CustomerService node defined in the guide (or instruct the reader to load the Service Catalog schema that provides ServiceGeneric).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/docs/ipam/build-your-ipam-schema.mdx, line 159:

<comment>The extensions example pairs `IpamIPPrefix` with `peer: ServiceGeneric`, but nothing in this guide defines or loads `ServiceGeneric` — the first example's node is `Service` in the `Customer` namespace (`CustomerService`). Copying the block verbatim will fail schema load. Point the peer at the `CustomerService` node defined in the guide (or instruct the reader to load the Service Catalog schema that provides `ServiceGeneric`).</comment>

<file context>
@@ -0,0 +1,169 @@
+    - kind: IpamIPPrefix
+      relationships:
+        - name: service
+          peer: ServiceGeneric
+          cardinality: one
+          direction: outbound
</file context>
Suggested change
peer: ServiceGeneric
peer: CustomerService

namespace
namespaces
nats
netmask

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Custom agent: Flag AI Slop and Fabricated Changes

The added netmask entry duplicates the existing entry after Neo4j. Remove one of the two entries to keep the spelling exception list unique.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .vale/styles/spelling-exceptions.txt, line 164:

<comment>The added `netmask` entry duplicates the existing entry after `Neo4j`. Remove one of the two entries to keep the spelling exception list unique.</comment>

<file context>
@@ -153,13 +153,15 @@ menu_placement
 namespace
 namespaces
 nats
+netmask
 Nautobot
 Neo4j
</file context>


**Transient database errors.** `SchemaMigration` and `GraphMigration` own their transaction and carry `retry_db_transaction`, so a transient error (a deadlock, or an entity a concurrent migration removed) is replayed on a fresh transaction with backoff and jitter instead of failing the migration; their inner query loops let those errors propagate to the transaction owner. This matters when migrations of the same kind run concurrently (schema path migrations execute as a batch). The catch-all `except` shown above belongs to `ArbitraryMigration`/`execute` overrides that run serially during upgrade and record every error as a failed `MigrationResult`; if you override `execute` to own a transaction on a concurrent path, add `retry_db_transaction` and do not catch retriable errors inside the transaction. See [Database Schema — Transaction Retry](../../knowledge/backend/database-schema.md#transaction-retry).

**Overriding `execute` discards the decorator.** A `SchemaMigration` subclass that overrides `execute` to open its own transaction must reapply `@retry_db_transaction(name="schema_migration")` — `node_relationship_remove.py` is the worked example. A subclass that only guards a condition and then delegates to `super().execute(...)` is already covered by the base and must not add it again, or the two retries nest.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The doc tells the reader to reapply @retry_db_transaction(name="schema_migration") and cites node_relationship_remove.py as the worked example, but that file's decorator actually uses name="relationship_remove_schema_migration" (node_relationship_remove.py:260). Align the doc with the cited example (or drop the explicit name) so the retry metric label matches the code it points to, instead of colliding with the base class's schema_migration retry counter.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At dev/guides/backend/creating-migrations.md, line 73:

<comment>The doc tells the reader to reapply `@retry_db_transaction(name="schema_migration")` and cites `node_relationship_remove.py` as the worked example, but that file's decorator actually uses `name="relationship_remove_schema_migration"` (node_relationship_remove.py:260). Align the doc with the cited example (or drop the explicit name) so the retry metric label matches the code it points to, instead of colliding with the base class's `schema_migration` retry counter.</comment>

<file context>
@@ -68,6 +70,8 @@ Set `minimum_version` to the current `GRAPH_VERSION` (the migration runs when up
 
 **Transient database errors.** `SchemaMigration` and `GraphMigration` own their transaction and carry `retry_db_transaction`, so a transient error (a deadlock, or an entity a concurrent migration removed) is replayed on a fresh transaction with backoff and jitter instead of failing the migration; their inner query loops let those errors propagate to the transaction owner. This matters when migrations of the same kind run concurrently (schema path migrations execute as a batch). The catch-all `except` shown above belongs to `ArbitraryMigration`/`execute` overrides that run serially during upgrade and record every error as a failed `MigrationResult`; if you override `execute` to own a transaction on a concurrent path, add `retry_db_transaction` and do not catch retriable errors inside the transaction. See [Database Schema — Transaction Retry](../../knowledge/backend/database-schema.md#transaction-retry).
 
+**Overriding `execute` discards the decorator.** A `SchemaMigration` subclass that overrides `execute` to open its own transaction must reapply `@retry_db_transaction(name="schema_migration")` — `node_relationship_remove.py` is the worked example. A subclass that only guards a condition and then delegates to `super().execute(...)` is already covered by the base and must not add it again, or the two retries nest.
+
 ### Step 2: Bump `GRAPH_VERSION`
</file context>
Suggested change
**Overriding `execute` discards the decorator.** A `SchemaMigration` subclass that overrides `execute` to open its own transaction must reapply `@retry_db_transaction(name="schema_migration")` `node_relationship_remove.py` is the worked example. A subclass that only guards a condition and then delegates to `super().execute(...)` is already covered by the base and must not add it again, or the two retries nest.
**Overriding `execute` discards the decorator.** A `SchemaMigration` subclass that overrides `execute` to open its own transaction must reapply `@retry_db_transaction` with a migration-specific name `node_relationship_remove.py` reapplies it as `@retry_db_transaction(name="relationship_remove_schema_migration")` and is the worked example. A subclass that only guards a condition and then delegates to `super().execute(...)` is already covered by the base and must not add it again, or the two retries nest.

# focus, while hover goes through the warm-up delay — the deterministic
# option for CI.
await read_only_page.get_by_role("button", name="Where this value comes from").first.focus()
await expect(read_only_page.get_by_text("From the organisation default")).to_be_visible()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The test verifies only that an organisation default exists and its source hint appears, not that the correct value is inherited. The tooltip text is From the organisation default: <value> and the substring assertion passes for any value, so a regression that inherits the wrong format would not be caught. Assert that the inherited value equals GLOBAL_DATE_FORMAT on the read-only page (and optionally that the ISO browser default is not shown) to give the test its stated purpose.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/e2e/preferences/test_global_preferences.py, line 66:

<comment>The test verifies only that an organisation default exists and its source hint appears, not that the correct value is inherited. The tooltip text is `From the organisation default: <value>` and the substring assertion passes for any value, so a regression that inherits the wrong format would not be caught. Assert that the inherited value equals GLOBAL_DATE_FORMAT on the read-only page (and optionally that the ISO browser default is not shown) to give the test its stated purpose.</comment>

<file context>
@@ -0,0 +1,66 @@
+        # focus, while hover goes through the warm-up delay — the deterministic
+        # option for CI.
+        await read_only_page.get_by_role("button", name="Where this value comes from").first.focus()
+        await expect(read_only_page.get_by_text("From the organisation default")).to_be_visible()
</file context>

@ogenstad
ogenstad marked this pull request as ready for review August 20, 2026 09:00
@ogenstad
ogenstad requested review from a team as code owners August 20, 2026 09:00
@ogenstad
ogenstad merged commit e5ee9c8 into develop Aug 20, 2026
116 of 119 checks passed
@ogenstad
ogenstad deleted the pog-release-1.11-to-develop branch August 20, 2026 09:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

group/backend Issue related to the backend (API Server, Git Agent) group/ci Issue related to the CI pipeline type/documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants