test(events): prove an event on the maximum is rejected after the run-context append [IFC-3008] - #10249
test(events): prove an event on the maximum is rejected after the run-context append [IFC-3008]#10249ogenstad wants to merge 63 commits into
Conversation
GroupMutatedEvent.get_related() built an unbounded related-resources list (2 entries per member, 3 per ancestor), so a single mutation changing a few hundred members exceeded the Prefect maximum and the whole event was silently dropped: no activity log, no membership automations. Members and ancestors are now a single entry each (dropping the duplicate related.node and the dead group.update roles) and the list is truncated at the Prefect maximum, so the event is always recorded. The event query filter and related-nodes output read all three related-node roles and dedupe by id, keeping output identical across the consolidated format and older events still in Prefect retention. Fixes #10127
The resource loop in sort_key wrote the resource priority to type_cost instead of item_cost. Every item carries both a type marker and a resource marker, so that loop always ran and always erased the type priority the first loop had computed. Items were therefore sorted by resource kind only, and an integration test could run before the smoke test of the same resource. Nothing covered the ordering, so add unit tests on the collection hook.
Bumps [dorny/paths-filter](https://github.com/dorny/paths-filter) from 4.0.2 to 4.0.3. - [Release notes](https://github.com/dorny/paths-filter/releases) - [Changelog](https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md) - [Commits](dorny/paths-filter@v4.0.2...v4.0.3) --- updated-dependencies: - dependency-name: dorny/paths-filter dependency-version: 4.0.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
…le/dorny/paths-filter-4.0.3 chore(deps): bump dorny/paths-filter from 4.0.2 to 4.0.3
The "Example:" preview and the (i) source tooltip in the preferences forms formatted through an ad-hoc date-fns format() call that took no timezone, so they showed browser-local time — off by the zone offset, and at some hours by a whole calendar day (with ISO_8601 they also stated a false offset). The duplicate formatter is deleted; previews now render through the shared preference-aware mechanism, exposed as formatWithPreferences, using the unsaved timezone the form currently holds. The user form falls back to the effective inherited zone, while the global editor falls back to the browser zone, since an unset global timezone means "browser default" for every viewer. Fixes #10175 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ative DateDisplay's compact/relative heuristic compared the day difference with a signed value, so any future date — however distant — fell through to the relative branch and rendered as "in 4 years" instead of the user's preferred date format. The window is now a week either side of now, keeping the relative phrase where it reads best and the preferred date beyond. Fixes #10173 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The object detail page's DateTime attribute renderer entered DateDisplay's metadata-oriented age heuristic, so user-authored timestamps collapsed to a bare date (or a relative phrase) while the list view showed the full preferred datetime. The detail renderer now opts out with fullTimestamp, matching the table cell. Fixes #10172 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 4 to 5. - [Release notes](https://github.com/codspeedhq/action/releases) - [Changelog](https://github.com/CodSpeedHQ/action/blob/main/CHANGELOG.md) - [Commits](CodSpeedHQ/action@v4...v5) --- updated-dependencies: - dependency-name: CodSpeedHQ/action dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/stale](https://github.com/actions/stale) from 10 to 11. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](actions/stale@v10...v11) --- updated-dependencies: - dependency-name: actions/stale dependency-version: '11' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
… cleared The date-format example fell back to the caller's effective timezone while the form's timezone field was empty. When that effective value came from the caller's own override, clearing the override previewed the zone being removed rather than the one about to apply. The fallback is now the inherited zone, expressed as a domain rule: a USER source resolves to null, since the API resolves the inherited value away once an override wins and the browser zone is the only honest stand-in. Also trims the comments added across these fixes down to the load-bearing rationale, per the repo's code-documentation style. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s only when exposed (#10195)
…preview inheritedTimezone excluded a USER source but let any other source through, so a DEFAULT source carrying a value would have been reported as inherited — contradicting the pattern resolver, which discards a DEFAULT value even when one is present. Only GLOBAL is an inherited layer, so whitelist it. No behaviour change against today's API, which always pairs DEFAULT with a null value; this keeps the two rules consistent if that ever loosens. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A worker without a local copy of a repository clones it on demand. That path only carries the repository id and name, so the repository object never learns which git ref to track. It fell back to the platform default branch. Git then failed on any repository whose default branch is not `main`, the repository went to error, and artifact generation failed with it. It takes more than one worker to see this. Each worker keeps its own git directory, so only the worker that added the repository has the clone. `InfrahubRepositoryBase` gets one abstract method, `resolve_checkout_ref`. The re-clone path calls it and passes the result to `create_locally`. `CoreRepository` reads `default_branch`, `CoreReadOnlyRepository` reads `ref`. The lookup only runs when the clone is missing, so an existing clone costs nothing extra. Fixes #8749
…ferences-issue-ce071b
Both branch-details suites assert that no "default" text is visible on a non-default branch, to prove the default badge is absent. The assertion used a substring match, so renaming the attribute label to "Schema differs from default branch" made it match that label instead and the tests failed for a page that is rendering correctly. The positive assertion was updated with the rename; this negative one was missed. Matching exactly still catches a stray badge, whose text is only "default", without tripping on prose that contains the word. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(backend): delete branch data in bounded batches Deleting a large branch ran as a single Cypher statement whose peak transaction memory scaled with the size of the branch: it collected the element id of every vertex touched by a deleted edge into two lists and concatenated them. On a big enough branch that exceeded dbms.memory.transaction.total.max, and because the inner writes committed in batches the failure left the branch stranded in DELETING -- invisible in the branch list -- with most of its data still in the graph. Replace it with a set of bounded queries driven from Python by a new BranchDeleter component: the agnostic peers of branch-only nodes first, then one batch of edges per relationship type until none are left. Peak transaction memory is now a function of the batch size rather than the branch, and naming the relationship type lets the branch range index serve the match instead of scanning every edge in the database. Each batch also deletes the vertices its edge deletions left bare. That is only sound because every branch edge is removed by the batch's DELETE and both endpoints are re-examined afterwards, so the batch that removes a vertex's last edge is the one that sees it at degree zero. A DETACH DELETE would break it by removing edges that never reach a batch of their own, stranding the vertices on their far side. Branch.delete now raises instead of silently dropping only the Branch vertex; callers use BranchDeleter. On a branch of 1,074,217 edges this deletes the same 416,021 vertices as before, in 27.8s rather than 87.9s, and completes at a 512 MiB transaction limit where the previous implementation ran out of memory. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(backend): finish deleting branches abandoned in DELETING A branch delete that ran out of transaction memory committed part of its work before failing, leaving the branch with the DELETING status and the rest of its data in the graph. That state was unreachable: the branch is filtered out of the branch list, and Branch.get_by_name hides it by default, so the delete could not be retried and the space could not be reclaimed. Add graph migration 075, which finds every branch still in DELETING and runs it through BranchDeleter, then removes the branch node. It reuses the normal delete path rather than reimplementing it, loading each branch via the ignore_deleting escape hatch. Finding them needs a dedicated query because the shared branch list query filters DELETING out unconditionally. BranchDeleter now returns the number of edges it removed so the migration can report progress on the migration console. `infrahub db migrate` raises the infrahub log level to WARNING, which hides the deleter's own logging, and deleting a large branch takes long enough that silence looks like a hung upgrade. The migration is a no-op where no branch is in DELETING, and safe to re-run: an interrupted delete resumes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(backend): allow retrying a branch delete left in DELETING A delete that failed part way through set the DELETING status before doing any cleanup, and both the BranchDelete mutation and the branch-delete flow looked the branch up with the default lookup, which hides that status. The retry reported the branch as missing, so the only way to reclaim the data was the upgrade migration -- which runs once, leaving any later failure stranded for good. Both lookups now pass ignore_deleting=False. Two consequences worth knowing: the mutation will accept a delete for a branch whose delete is still running, and nothing serialises the two runs (each query involved is idempotent); and a retry re-emits BranchDeletedEvent. Making the surrounding flow steps idempotent is left for later. Cap the agnostic cleanup at 500 rows rather than query_size_limit. Those batches count Nodes, and each one can drag an unbounded number of peer vertices into the transaction with it, unlike the edge batches where one row is one edge. 500 is what this phase used before the batching work, and raising it to 5000 was incidental rather than deliberate. Drop the edge accounting comment claiming the vertex cleanup can remove extra edges. That was true while the cleanup used DETACH DELETE; it now only deletes vertices that are already bare. Add the missing tests: Branch.delete refusing, the default/global guards that moved onto BranchDeleter, and retrying a branch left in DELETING. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(backend): own the branch in the delete-retry test The test created the branch with `create_branch`, which leaves created_by as the system user, so the BranchDelete mutation took its permission branch and the assertion depended on how a super-admin grant resolves against a specific DELETE_BRANCH check. That resolved differently in CI and the test failed with PermissionDeniedError. Use the existing first_account / session_first_account fixtures and make that account the branch owner, so created_by matches the requesting account, the permission check is skipped, and the result turns on the DELETING status alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(backend): make the DELETING branch cleanup migration robust Address review findings on migration 075 and the deleter's accounting. The migration's branch lookup suppressed the generated pagination. A read query with no limit of its own is executed page by page, so with the SKIP/LIMIT suppressed every page re-read the whole set and the paging never reached a short page: with at least query_size_limit stranded branches the upgrade would never finish. Pagination is enabled again and the lookup orders by branch name, which is unique, so the pages are disjoint. Each branch is now deleted in its own try. A failure is reported as "branch '<name>': <error>" and the loop continues, so an operator gets the names of everything that still needs a re-run instead of the first exception and an unknown remainder. Failing to list the branches at all still aborts, since there is then nothing to iterate. Cap the agnostic cleanup at min(batch_size, 500) rather than a fixed 500, so lowering the configured batch size to fit a constrained database is not answered with a larger batch than was asked for. Count the agnostic cleanup's edges towards the total the deleter reports. It detaches peer vertices, so leaving it out made the migration's per-branch progress undercount for any branch with agnostic data. The 075 test asserted the stalled branch's node was unreachable from main, which it always was -- it only ever existed on that branch, so the assertion held whether or not the migration ran. It now reads the node on its own branch, before and after. A third case covers one branch of three failing, using a FailingBranchDeleter that delegates to the real deleter for the others so the test proves they were reclaimed rather than merely attempted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(backend): only run post-delete work when this attempt removed the branch BranchDeleter.delete returned the number of edges it removed, which only the upgrade migration used, for its progress output. It said nothing about whether this attempt was the one that removed the branch -- so now that a delete can be retried, two attempts on the same branch could both go on to cancel the proposed changes, emit BranchDeletedEvent and delete the Git branch. Return a BranchDeleteResult carrying both branch_deleted and edges_removed. Removing the vertex is itself the claim: two attempts are serialised on it, so exactly one reports nodes_deleted, with no window of the kind a read followed by a delete would leave. The branch-delete flow returns early when it did not make the claim, and the migration reports whether the branch was still there. Skip the DELETING status write when the status is already set. It is a wasted query for a branch whose earlier delete failed part way through, and it fails outright if the branch has meanwhile been removed -- which made delete() unsafe to call twice at all, whatever it returned. A narrower window remains: an attempt that has not yet written the status, and whose write lands after another attempt removed the vertex, still raises rather than reporting false. It fails the run instead of double-processing, and closing it properly wants a branch-scoped lock. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * make sure branch is deleted from git during concurrent deletes * move delete_branch flow logic to a new component for easier testing * formatting * add default/global branch delete guard at higher level --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
update Branch.get_by_name to include ignore_deleting=True so that a delete can be retried
pass the "low_context" into the branch delete orchestrator for follow-up tasks
Keep stable's behavior from #10195 on top of the 1.11 frontend architecture: git-repository.tsx links to the node's concrete __typename using the moved NodeCore/object-urls modules, and the profiles-field test is ported onto getObjectForEditingFromApi, which replaced generateObjectEditFormQuery. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…C-3001] The git agent relied on the SDK client default for sync_with_git when creating an imported branch in the graph. That default flipped from True to False in the SDK, so branches discovered on a repository remote were created without the flag. Merging such a branch, running its repository checks and generating its artifacts are all gated on sync_with_git, so the git side of a merge was silently skipped: the git branch was never merged into the repository default branch and the recorded commit never advanced. Closes #10208
…d-run A dependency discovered after Vite's initial scan triggers a re-optimization reload that resets vi.mock, which surfaces as "mockClear is not a function" in whichever spec happens to be running. CI hit it on two different files across consecutive runs. @radix-ui/react-dropdown-menu is a declared app dependency the scan misses, so pre-bundling it removes that trigger. Two entries already in this list, @dagrejs/dagre and html-to-image, still reload: they belong to a workspace package consumed as source and do not resolve from the app, so pre-bundling silently skips them. Fixing that needs them declared here, which is a dependency change of its own. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(core): cap and consolidate group mutation event related resources
…10215) BusRecorder is a recording double only; its reply/rpc raises are should-not-be-called guards, not a failing double. Present the recording and failing doubles as the two patterns to write, with BusRecorder as the recording example, rather than claiming BusRecorder illustrates both. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…--file (#10217) In --file mode, `infrahubctl object update` still requires KIND and IDENTIFIER as positional arguments even though their values are ignored and the file supplies the targets. State both facts so a reader doesn't assume the positionals can be omitted. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(rebase): use the branch-creation schema as the migration baseline This is a backport of the behaviour introduced upstream in fd31b14, which arrived inside a MergeSchemaAnalyzer refactor that does not apply to stable. Only the behaviour is ported, not the refactor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(rebase): restore the branch's own schema when a rebase rolls back SchemaUpdateCoordinator used a single origin_schema for two unrelated jobs: the baseline the migrations compare against, and the schema restored into the registry when the update fails. Those coincide for a plain schema update, but not for a rebase, where the baseline has to be the common ancestor. A rebase that failed during its migrations therefore rolled the branch back to the schema it was created from, silently dropping any schema change made on the branch and persisting a wrong schema hash with it. Split the parameter into migration_baseline_schema and rollback_schema, both required so the two roles cannot be conflated again. The three call sites where they genuinely coincide pass the same value twice; only the rebase differs, capturing the branch's registry schema before the graph is rebased. The graph side already behaves: RollbackQuery reverses edges stamped with the unified timestamp, and the rebase shares that timestamp, so the branch's data is back to its pre-rebase state and the branch's own schema is the consistent thing to pair with it. Covers both schemas the coordinator is handed in one component test, driving the real rebase flow twice, once succeeding to observe the migration baseline and once failing to observe the rollback. The two cases share the fork-before-inheritance setup, which dominates the runtime. WorkflowRecorder gains an execute_results hook so a test can make a workflow report errors without patching. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * only get the schema from the database when necessary * update SchemaUpdateCoordinator docstring * verify hash is rolled back during failed rebase --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: release 1.10.7 * add another commit and update changelog/release notes --------- Co-authored-by: Patrick Ogenstad <patrick@ogenstad.com>
Keep release-1.11's refactored SchemaUpdateCoordinator (GraphRollbacker, timestamp-scoped rollback) and the branch merge orchestrator in place of stable's pre-refactor shape, and forward-port the half of the rebase backport that release-1.11 did not already cover: the schema restored into the registry on failure is now passed separately from the migration baseline, so a failed rebase puts back the schema the branch itself had rather than the common ancestor it migrated against. rollback_schema is required at every call site. WorkflowRecorder keeps the unified calls list and gains the canned execute_results the incoming rebase test relies on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nt package The file merged cleanly from stable but kept importing PrefectEventData and InfrahubEventFilter from the modules that the event package split replaced, so nothing resolved at import time. Caught by ty, which resolves imports where ruff and the mypy configuration do not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Stable to release 1.11 (w/ resolved conflicts)
The Marketplace docs page only linked to the live marketplace once, inside the fetch instructions. Link it from the intro, point the find-schema step at the browse catalog, link the example identifiers to their detail pages, and list the marketplace under related resources. Co-authored-by: Yvonne Jouffrault <yvonne@opsmill.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Merge stable into release-1.11
…nd (#10180) * fix: advance number pool past values already present on the target kind A CoreNumberPool derived its next value only from its own reservations, so a value already present on the target kind (created directly or brought in by a brownfield import) was invisible. The pool offered it, the uniqueness constraint rejected the save, and since the failed allocation reserved nothing the pool re-offered the same value on every attempt, parking permanently at that value. Allocation now excludes values already held on the target kind, matching the uniqueness-constraint validator's visibility (is_isolated=False, deletions), and only for globally unique attributes so per-relationship and non-unique attributes stay fully allocatable. closes #10179 * refactor: address review on number pool taken-value query - Drop the toInteger cast on the range predicate so it can use the value index (av.value is a native integer, as NumberPoolGetAllocated relies on). - Add WITH DISTINCT n, attr, av before the CALL so it runs once per (n, attr, av) instead of once per edge pair. - Condense the guard comment. - Remove an em dash from the changelog fragment. * refactor: group number pool taken-value query by attribute and dedupe tests Group the taken-value lookup by (n, attr) and return the latest active value per attribute, dropping the unused ha/hv bindings and re-applying the range filter after the subquery. Extract the shared ticket-schema and pool setup into a fixture. * test: load number pool schema once via class-based allocation tests Group the three shared-setup allocation tests into a class whose class-scoped fixture loads the ticket schema and pool a single time, instead of reloading per test. The methods run in definition order and build on shared data.
#10234) * fix: forbid unknown fields in the exported node JSON Schema [INFP-234] SchemaLoadAPI now derives from the generated write models, which set extra="ignore" because unknown-field policy is applied imperatively when a schema is loaded. The exporter was never revisited, so the published document lost every additionalProperties: false it used to inherit from the internal models' extra="forbid", and an editor validating against it accepted a typo the load endpoint rejects. Close every object in the exported document, and declare the read-only fields alongside it marked deprecated rather than omitting them: the load endpoint accepts one, drops the value and reports a warning, so a closed document that left them out would turn a schema read back from Infrahub into a file full of errors. The read-only names come from the generated contract table, the same one the load endpoint validates against, so the document cannot drift from the endpoint's verdict. Also drop AttributeSchema.model_json_schema. It set out to bind a kind to its parameters for the language server but never reached the published document: pydantic does not call a nested model's classmethod when building the parent, and its refs were Draft-7 style that would not resolve in a $defs document. The write models' discriminated union does that job now. Regenerating openapi.json after removing it produces no diff. * fix: annotate the validator fixture against the jsonschema protocol * test: assert the invariant hardening relies on, and pin each rejection reason Hardening reaches the root and $defs only, which covers the document while pydantic hoists every nested model. Nothing stated that, so a field typed as a mapping would render an inline object the sweep never sees. Assert it instead: such a field now fails the suite and gets a deliberate decision, rather than either leaving one spot open or having its value schema overwritten by a recursive sweep. Every case now pins the message the document must report, so a case cannot go green by failing for an unrelated reason. Drop the escape clause that excused a required read-only field from carrying the deprecation marker: no field reaches it, and it could only ever weaken the assertion. Count what each structural test asserted so an empty document cannot pass it vacuously. Adopt the project's dataclass test-case shape: field docstrings, a typed module-level constant, and IDs that name the expected outcome. * chore: declare jsonschema for tests and build the document without mutating The schema tests validate against jsonschema, which reached the environment only as a transitive dependency, so a change upstream could have removed it. Declare it in the dev group with a floor and no ceiling: the floor is the version whose validation messages the tests assert verbatim, and leaving the ceiling off keeps this pin from ever blocking an upgrade that wants a newer one. Build the hardened document from a copy rather than editing the one handed in. A function named for building a document should not leave the caller's own changed underneath it, and a test now holds that contract.
* reorganize learn folder * Move everything to learn * Cleanup - improvements - links * docs: rework Infrahub Labs hub per review — value-first intro, topic grouping, text links - Rewrite the labs overview: lead with what labs are for and what you gain, add when-to-use guidance, group standalone labs by topic (getting started vs advanced) with time estimates and when/why for each lab - Rename page and sidebar label from Labs to Infrahub Labs - Replace StandoutLink buttons with linked text at the end of each paragraph; remove the now-unused StandoutLink component - Say lab instead of tutorial on lab pages, now that Tutorials is a separate section Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * various fixes * Keep transformation naming consistent --------- Co-authored-by: Yvonne Jouffrault <yvonne@opsmill.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* docs(objects): add "Create objects" overview page
Introduces a single entry point listing every way to create objects
(web interface, infrahubctl, YAML object files, Python SDK, GraphQL
API, Generators, Infrahub Sync) and links it from the related pages.
Also drops the redundant "What you can do with objects" list from
overview.mdx and shortens a couple of page titles.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* docs(objects): give Create objects real per-method context
The page shipped as a routing table: it named each method and linked
out, but gave no basis for choosing and showed no example of any
method. It also stated the web UI button as "Create <Kind>" when the
button reads "Add <Kind>".
Each method now carries a paragraph on when to use it, and one running
example (InfraDevice atl1-edge1) is shown four ways through the shared
groupId="method" tabs, so a reader's interface choice persists across
the docs site. Generators and Infrahub Sync move out of the method
table into their own section, since they create objects from a
workflow rather than from a command. Adds sections on Object
Templates, Profiles, and resource pools, and on what happens after an
object is created.
Restores outbound navigation on the Objects hub as "## In this
section", matching the format used by the IPAM and Schema hubs. The
PR had removed the hub's only links to its own child pages.
Facts verified against models/base/dcim.yml (required attributes and
the site relationship), object-create-form-trigger.tsx (button and
panel labels), generators/overview.mdx (execution model), and the
Python SDK and Infrahub Sync docs.
* docs(objects): restructure Create objects into three categories
The page presented seven creation methods in three formats across four
locations: five in a table, four of those repeated as tabs, one as its
own section, and two more introduced only as intro bullets and then
described again below. Nothing shared a shape, so the methods could
not be compared.
Groups them under what drives the create — directly, from a file, from
a workflow — and gives every method the same three beats: when to use
it, an example or the mechanism, and where to read more. Drops the
table, since the useful content per method is a descriptive paragraph
rather than a cell value, and three categories are easier to hold than
seven rows. Drops the duplicated intro bullets.
Folds Object Templates, Profiles, and resource pools into "After an
object is created", framed as where an attribute's value came from,
which ties them to the metadata and lineage they are recorded in.
Follows the structure Kubernetes uses for the equivalent page
(Kubernetes Object Management), where parallel treatment of each
technique is what makes them comparable.
* docs(objects): fix voice regressions in Create objects
Words removed earlier had returned during the restructure. Fixes each
against the rules they broke:
Figurative usage: "a file that lives in Git" -> "stored in Git";
"what drives it" -> "what starts the work"; "three commands drive it"
-> "run it"; "objects follow from data" -> "are determined by data";
"circuits a service definition implies" -> "required by"; "a Group
naming the objects" -> "that lists".
Vague UI jargon: "appear as selectable controls" -> "pick an Object
Template, assign Profiles, and allocate from a resource pool", naming
what the reader does.
Features as the agent, in the section flagged for exactly this:
"Object Templates supply structure" -> "Pick an Object Template to
start the object with...". The reader is now the subject of all three.
Definition by negation: "without leaving the terminal" and "without
changes" removed. Unverifiable claims: "the quickest way", "actually
needs", "straightforward scripting". Overclaim: "Infrahub has four
interfaces" -> "Four interfaces create objects", since the REST API
exists too.
Splits the four Generator trigger conditions out of one long sentence
into a list.
* docs(objects): restore the category summary at the top
The restructure compressed the three categories into a single
sentence, so a reader had no way to see what each one is for before
scrolling into it.
Restores a three-item summary, each naming what the category is and
when to use it, linking to its section so a reader can go straight to
the one they need.
Reworks the "from a workflow" opener so it draws the distinction
between the two tools rather than repeating the summary: a Generator
works from data already in Infrahub, Infrahub Sync from data in
another system.
* docs(objects): make the category summary tell them apart
"Use this when you are deciding what to create as you go" gave a
reader nothing to choose on, and the three descriptions were not
comparable to each other.
Each now states the same three things, so they can be read against one
another: how many objects at a time, where the definition comes from,
and whether it happens once or keeps happening. The lead-in names
those axes instead of "what starts the work".
One at a time, manually or in a short script -> directly. Many
declared together and reviewed in Git -> from a file. Created and
updated as the source changes, without anyone issuing a command ->
from a workflow.
* docs(objects): make each section opener say what it is for
Applies the test used on the category summary to every section and tab
opener: say what the thing is for, be concrete, give the reader
something to decide on.
Object files led with "data that changes rarely", which describes a
property rather than a purpose. Now states what the file is and what
keeping it in Git buys: rebuild an instance from it, promote the same
dataset from development to production, review a data change in a
pull request. Names the data it suits — sites, roles, platforms,
device types, tags.
GraphQL was defined by negation ("when the client is not Python").
Now positive and concrete: any language or tool that can send an HTTP
request, with examples.
"Create objects directly" said the four interfaces are equivalent but
not what separates them; it now names the four contexts. The web
interface opener explains why starting there helps — the form shows
what a script will need to supply later. The workflow opener states
what both tools are for rather than only how they run.
Promotion between environments verified against
git-integration/multi-environment.mdx.
* docs(objects): fix three awkward openers
"Four interfaces create objects" made the interface the actor, the
same problem flagged earlier with features. Now "There are four ways
to create an object one at a time", and the abstract noun is gone.
"How you create objects depends on..." opened on a nominalization
instead of the fact the reader wants. Now leads with "There are three
ways to add objects to Infrahub", then the criteria for choosing.
The workflow opener started on "Both", referring to nothing yet
introduced in the section, and ended in "That is what suits them to
data no one should be maintaining manually — the objects that ought to
exist because something else is true", which said very little at
length. Replaced with the actual use case: some objects exist because
other data does and have to stay correct as it changes, so maintaining
them manually means repeating the work every time.
Uses "choose" rather than "pick" throughout.
* docs(objects): say what applies to which creation methods
"All three work whichever method created the object" left both halves
unclear: "work" stated nothing, and the clause did not say what the
methods were. Names the subjects and points back to the page's own
three categories, so the scope is explicit.
Also drops the negation in the lead-in — "so that you do not fill it
in each time" becomes "so the object receives it on creation".
* fix linting
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Yvonne Jouffrault <yvonne@opsmill.com>
…10237) The bulk recompute dispatcher tagged its run as a database change once it had values to write. add_tags rebuilds the whole tag list from the runtime snapshot taken when the run started, and a tag update never refreshes that snapshot, so this second call dropped the branch tag and every related-node tag the flow had added at its start. A recompute task then fell out of the task list filtered by branch and out of the task list of a node, which is why the tasks looked like they vanished one by one. The call was redundant anyway. Every flow that reaches the dispatcher already declares the database-change tag on its workflow definition, and Prefect merges the deployment tags into the run when it creates it. Drop the call.
* fix: narrow Python transform closure to its own file (closes #9644) A Python transform's and a generator's dependency closure was every git-tracked file in the directory holding its file_path. Sharing a directory between definitions - each sitting next to its own query and helper modules - therefore regenerated every artifact rooted there on any single-file edit, including edits to files the definition never used. Auto-detection now claims the entry file alone. Files a source genuinely depends on are declared through watch.files, where naming the containing directory restores the previous closure for that definition. A definition that has not declared watch still folds the commit id into its fingerprint, so no safety net is lost. * docs: fix release-note style and correct closure wording from review Vale enforces the branded plural over changelog fragments, which the release-note style job checks and the fragment failed. The docs style guide also forbids "transform" as a noun, which Vale only catches in the plural, so the singular slips in the fragment and in python.mdx are normalised too. The knowledge note claimed the untrusted-closure fallback was effectively dead for Python. A watch.files pathspec git cannot enumerate raises GitCommandError, which the aggregator isolates to complete=False, so both routes are now named. The component selection tests hand-set their stored closures rather than declaring watch, so their comments no longer credit watch.files for what the fixtures hardcode, and the two renamed tests state what they actually cover: the gate treats every closure member alike. * docs: attribute Python regeneration to the fingerprint, not an incomplete closure An undeclared Python Transformation was described under the incomplete-closure fallback, which is the wrong mechanism: its closure is always complete, and the regeneration comes from the fingerprint being tied to the current commit while no watch declaration exists. Three pages claimed the closure was incomplete or that declaring watch marks it complete; an empty files list never reaches the closure union at all. * docs: keep closure-completeness claims in one place The user-facing pages restated an internal invariant in their own words, which is how three review rounds each caught a different overstatement of it. They now describe only what a reader acts on, and mention dependencies_complete solely for Jinja2, where auto-detection really does report it. The knowledge note is the single place that spells out when a Python closure can be incomplete, and it now scopes that to misconfiguration rather than claiming the fallback is dead.
Merge stable into release-1.11
…hint (closes #10174) (#10198) * test: add failing test for 10174-timezone-preference-validation Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: validate preference timezone at write and correct the UI source hint Reject a non-IANA timezone when setting a user or global preference, so an unusable zone can no longer be persisted through the API/SDK. Validation runs at the write path only (resolving the value against the runtime zone database); the model stays lenient so a bad value stored earlier still reads back, and an empty value normalizes to unset so the write reply agrees with later reads. Correct the preferences screen so it no longer claims a stored zone is in effect when the viewer's browser cannot render it; the hint now reports the browser fallback instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: reject implementation-defined timezone keys at write A full system zone tree resolves entries like localtime, posix/*, and right/* that browsers reject and that no client should store as a preference. Reject them explicitly before construction so the guarantee holds regardless of which zone database the runtime ships (verified: the slim runtime resolves localtime). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: lock in construction-based acceptance of a canonical offset zone Guards against a future regression to an enumerated allowlist, which would reject a resolvable zone the runtime can apply. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: cover the GLOBAL-source unrenderable timezone hint The corrected hint fires for an org-default zone the browser cannot render, not only a user override. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: reject the posixrules pseudo-zone at write posixrules resolves in the runtime zone tree but is not a browser-renderable IANA zone; add it to the rejected keys alongside localtime and posix/right. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: assert the exact error message when rejecting a non-IANA timezone Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor: rename the timezone preference validator to validate_timezone The function validates by construction and returns the value unchanged, so the name now reflects that it validates rather than normalizes. Trims the docstring and notes the accepted set is runtime-dependent, best confirmed live. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…um [IFC-3008] (#10242) * fix(core): reserve headroom below the Prefect related-resources maximum [IFC-3008] Node and group mutation events truncated their related resources to exactly the configured Prefect maximum. Prefect's events worker then extends that list in place with run-context resources - flow run, task run, flow, deployment, work queue, work pool and one per flow-run tag - which skips the client-side validation, so the enlarged event arrives above the maximum. The Prefect API answers by closing the /events/in websocket rather than by dropping the single event. Both call sites now truncate to get_related_resource_budget(), which reserves a tenth of the maximum with a floor of 20. get_prefect_max_related_resources() keeps returning the raw maximum and stays the base for the submission chunk size, which is a different constraint. * fix(core): tie the run-context headroom to a single declared worst case [IFC-3008] The headroom floor and the worst-case append size the tests exercised were two independent numbers in two files, so raising one would leave the other behind and silently reinstate the overflow. MAX_RUN_CONTEXT_RESOURCES now declares the append size in one place, the reservation floor derives from it and the tests import it. The survival check also only ran at a maximum where the proportional reservation binds, leaving the floor untested. It now spans both sides of the reservation. Also name the task run in the changelog, matching the docstring.
…-context append [IFC-3008] The related-resource budget was covered only from the positive side: an event built on the budget still validates once Prefect has enlarged it. That passes just as well against a mechanism that never rejects anything, so it does not show the reservation earns its place. Add the matching control. An event built on the maximum instead has no room for the in-place append, lands above the limit and is refused, which is the failure the budget exists to prevent. Both run over the same maximums, so the pair holds on either side of the reservation.
There was a problem hiding this comment.
1 issue found across 1 file
Confidence score: 5/5
- In
backend/tests/unit/event/test_limits.py, duplicating most oftest_event_on_the_budget_survives_the_prefect_run_context_appendcreates maintenance risk: future changes may update one test path but not the other, causing inconsistent coverage and harder debugging—factor the shared setup/assertions into a helper or parameterized test to keep behavior aligned.
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="backend/tests/unit/event/test_limits.py">
<violation number="1" location="backend/tests/unit/event/test_limits.py:107">
P3: This new test duplicates nearly the entire body of `test_event_on_the_budget_survives_the_prefect_run_context_append` (both event-construction list, the `event.related +=` append, and the trailing len/validation logic). The only real differences are `get_prefect_max_related_resources()` vs `get_related_resource_budget()` as the build size and whether validation is expected to raise. Keeping the ~35 duplicated lines in sync means a change to the append or event shape must be applied twice, and the two tests drift apart silently. Extract a shared builder/helper that takes the build size, then have each test call it and assert its own outcome.</violation>
</file>
Shadow auto-approve: would not auto-approve because issues were found.
Re-trigger cubic
|
|
||
|
|
||
| @pytest.mark.parametrize("case", [pytest.param(case, id=case.name) for case in SURVIVAL_CASES]) | ||
| def test_event_on_the_maximum_is_rejected_after_the_prefect_run_context_append( |
There was a problem hiding this comment.
P3: This new test duplicates nearly the entire body of test_event_on_the_budget_survives_the_prefect_run_context_append (both event-construction list, the event.related += append, and the trailing len/validation logic). The only real differences are get_prefect_max_related_resources() vs get_related_resource_budget() as the build size and whether validation is expected to raise. Keeping the ~35 duplicated lines in sync means a change to the append or event shape must be applied twice, and the two tests drift apart silently. Extract a shared builder/helper that takes the build size, then have each test call it and assert its own outcome.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/tests/unit/event/test_limits.py, line 107:
<comment>This new test duplicates nearly the entire body of `test_event_on_the_budget_survives_the_prefect_run_context_append` (both event-construction list, the `event.related +=` append, and the trailing len/validation logic). The only real differences are `get_prefect_max_related_resources()` vs `get_related_resource_budget()` as the build size and whether validation is expected to raise. Keeping the ~35 duplicated lines in sync means a change to the append or event shape must be applied twice, and the two tests drift apart silently. Extract a shared builder/helper that takes the build size, then have each test call it and assert its own outcome.</comment>
<file context>
@@ -100,3 +101,36 @@ def test_event_on_the_budget_survives_the_prefect_run_context_append(
+
+
+@pytest.mark.parametrize("case", [pytest.param(case, id=case.name) for case in SURVIVAL_CASES])
+def test_event_on_the_maximum_is_rejected_after_the_prefect_run_context_append(
+ case: SurvivalCase, monkeypatch: pytest.MonkeyPatch
+) -> None:
</file context>
Why
Follow-up to #10242, taking a review suggestion from @polmichel that arrived after it merged.
That PR covered the related-resource budget from one side only: an event built on the budget still
validates after Prefect's events worker enlarges it in place. On its own that assertion would pass
just as well against a mechanism that never rejects anything, so it does not demonstrate that the
reservation earns its place.
Goal: pair it with the control that shows the failure being prevented is real.
Non-goals: no production code changes, and no revisiting of how the reservation is sized.
What changed
One new test in
backend/tests/unit/event/test_limits.py. An event built on the maximum ratherthan the budget has no room for the in-place run-context append, so the enlarged event lands above
the limit and no longer validates:
It reuses
SURVIVAL_CASES, so it runs at 500 / 200 / 100 - the proportional branch of thereservation, the point where the two are equal, and the floor branch - matching its companion.
The
matchis pinned to the case's configured maximum rather than\d+, per.agents/rules/testing-python.md: match a substring only where the variable part genuinely cannotbe pinned down, and here it can.
How to review
The two tests are deliberately near-identical - they differ only in the size the event is built at
and in whether validation is expected to succeed. That symmetry is the point, so I left the ~18
duplicated lines rather than extracting a builder. Say the word if you would rather see the shared
construction pulled into a helper.
How to test
15 tests, all passing.
invoke formatandinvoke lint(ruff, ty, mypy) clean.Impact & rollout
Checklist
ci/skip-changelogdev/knowledge/backend/events.md