chore: merge main into release/v1.3.0 to resolve conflicts#621
Closed
olivermeyer wants to merge 39 commits into
Closed
chore: merge main into release/v1.3.0 to resolve conflicts#621olivermeyer wants to merge 39 commits into
olivermeyer wants to merge 39 commits into
Conversation
…and metadata [PYSDK-82] (#570) * chore: link PYSDK Jira board in Compass component * chore: add BetterStack status page link to Compass component * chore: update compass.yml with links, tier, lifecycle and metadata * chore: trigger Jira GitHub App webhook PYSDK-82
…DK-84] (#572) * chore: add Samia Service dependency to compass.yml PYSDK-84 * chore: add console dependencies and set process level in compass.yml PYSDK-84 * chore: add #python-sdk-dev Slack channel link to compass.yml PYSDK-84 * chore: add Sentry project link to compass.yml PYSDK-84
…DK-88] (#576) * chore: fix link types and add missing Ketryx link in compass.yml PYSDK-88 * chore: add board ID 1799 to Jira board URL in compass.yml PYSDK-88
…578) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The label uses colons as separators, not underscores. The wrong name caused the label to not be found when creating PRs. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(dev): update scalene profile target for v2 API
Scalene v2 requires explicit `run` and `view` subcommands instead of
the unified invocation. Also write profile output to tmp/ and add
scalene-profile.{json,html} to .gitignore.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(dev): create tmp/ dir before writing scalene profile output
Addresses Copilot review suggestion: tmp/ is gitignored and may not
exist on a fresh clone, causing make profile to fail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…r bounds PYSDK-93 (#580) Raises lower bounds in pyproject.toml for every runtime and runtime-optional package that Renovate/Dependabot bumped in uv.lock since January 2026, closing the downstream-exposure gap for SDK consumers (uvx aignostics, uv add aignostics, pip install aignostics, etc). Re-verifies every existing --ignore-vuln entry against current upstream fix status; removes four stale ignores that upstream fixes already made obsolete; keeps one live acceptance (CVE-2026-3219 on pip, awaiting 26.1 release) with documented rationale. Adds KNOWN_VULNERABILITIES.md as the auditable registry of live acceptances and remediation history, linked from SECURITY.md and the README footer. Every new lower bound is <= the currently-locked version in uv.lock, so no dependency is upgraded and no runtime behaviour changes for our dev/CI env. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…[PYSDK-94] (#581) Adds 14 new labels to .github/labels.yml across SOP, type, security, scope, and auto-merge axes. The existing labels-sync.yml workflow fires on push to main touching this file, so the labels become live as soon as this PR merges — no manual `gh label create` needed (labels were already pre-created to enable retroactive labelling of today's PRs). - sop:pr-sop-01, sop:cc-sop-01 — SOP trail on the PR itself - type:feature|fix|chore|refactor|docs|test|perf|build|ci — conventional-commit taxonomy extending legacy bug/documentation/enhancement (which remain) - security, security:supply-chain — orthogonal security axis - scope:sdk-consumers, scope:dev-only — consumer-vs-dev-only exposure - auto-merge — eligible for auto-merge once CI is green Rejected during review: release:breaking / release:no-impact (semver covers it), security:sbom (rolls up into chore or supply-chain), scope:docs-only (duplicates type:docs), scope:infra (overlaps type:chore / type:ci). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
….11.6 PYSDK-98 (#583) PYSDK-93 bumped pyproject.toml uv>=0.11.6 for GHSA-pjjw-68hj-v9mw and regenerated uv.lock with uv 0.11.x lockfile syntax. The Dockerfile pin at ghcr.io/astral-sh/uv:0.9.18 was not updated, so every Docker build step now fails with `uv sync --frozen` exit code 2 on lockfile parse. Bump to 0.11.7 to match what CI installs on the host. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…K-96] (#582) * chore: enable aignostics/claude-plugins qms plugin project-wide PYSDK-96 Adds extraKnownMarketplaces.aignostics-claude-plugins and enabledPlugins['qms@aignostics-claude-plugins'] to .claude/settings.json so every contributor auto-installs the qms plugin (cc-sop-01, pr-sop-01, sw-sop-01, audit-vulnerabilities, jira-api-conventions) on opening the repo in Claude Code, eliminating skill drift between team members. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: remove stale context7 ref from enabledMcpjsonServers PYSDK-96 context7 was not registered in the project's .mcp.json (which is empty) and only appeared in .vscode/mcp.json under the different name upstash/context7. Claude Code reads .mcp.json, not .vscode/mcp.json, so this entry did nothing and just confused reviewers. Empty the array so it's unambiguous that no project-level MCP servers are auto-enabled today — enableAllProjectMcpServers: true still picks up whatever is registered in .mcp.json in the future. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…103] (#591) Pyright was recursing into local Claude Code agent worktree dirs at `.claude/worktrees/<random-name>/` and reporting type errors from the nested codegen output, causing `make lint` to fail in working trees that have an in-flight Claude Code background session. Worktrees are per-developer ephemeral state, not source code. Two-line fix: - pyrightconfig.json: add `**/.claude/worktrees/**` to exclude - .gitignore: add `.claude/worktrees/` (belt-and-braces; without this a `git add -A` could accidentally scoop the worktree dir into a commit) Note: .claude/settings.json is intentionally tracked (it carries the aignostics/claude-plugins qms plugin pin from PYSDK-96 / #582); the gitignore entry is narrow (`/worktrees/` only) and does not affect that file. Supersedes PYSDK-102, which was a retroactive CR opened on PR #590 (closed unmerged). #590's branch and commit message did not contain the PYSDK key — CC-SOP-01 requires the key in both for the Jira GitHub App auto-link and smart-commit transitions to work. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ly audit-vulnerabilities routine) [PYSDK-104] (#592) Removes the explicit `pygments>=2.20.0` lower bound from pyproject.toml. The bound was added in PYSDK-93 to protect downstream consumers from CVE-2026-4539. Removing it intentionally re-introduces the consumer-side gap so the daily audit-vulnerabilities routine has work to do on its next scheduled run, and we can confirm the routine end-to-end: * detects the missing lower bound for a known accepted CVE * opens a CR ticket and PR per cc-sop-01 / pr-sop-01 * lifts the bound back to >=2.20.0 uv.lock is intentionally NOT modified — pip-audit (which scans uv.lock, not pyproject.toml) will continue to pass. Only the consumer-resolver visibility is reduced. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…YSDK-106] (#594) * chore(ci): emit claude:review:passed/failed label from automated PR review [PYSDK-105] Adds a machine-readable verdict from the automated Claude PR review so branch-protection rules can gate `auto-merge` on the review outcome without human intervention. Changes: * `.github/labels.yml`: two new mutually-exclusive labels - `claude:review:passed` (green) — no blocking findings on current head - `claude:review:failed` (red) — blocking findings on current head * `.github/workflows/claude-code-automation-pr-review.yml`: extend prompt with a mandatory "Machine-Readable Verdict" final step. Claude must emit PASS or FAIL based on the existing CRITICAL CHECKS criteria (test markers, coverage ≥85%, lint clean, conventional commits, architecture/security) and apply the corresponding label via `gh pr edit --add-label ... --remove-label ...`. The opposite label is always removed, so the labels stay mutually exclusive across re-reviews on subsequent pushes. Re-emission: every push to a PR re-runs the review (existing `synchronize` trigger), so the verdict is always tied to the current head commit — stale verdicts cannot persist. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(deps): bump pygments lower bound to >=2.20.0 for CVE-2026-4539 [PYSDK-106] Re-adds the `pygments>=2.20.0` lower bound to `[project].dependencies` transitive overrides in pyproject.toml. The bound was deliberately removed in PYSDK-104 (PR #592) to give the daily audit-vulnerabilities routine a real gap to remediate; this PR closes that gap. The locked version in uv.lock (pygments 2.20.0) already protects our dev/CI env from CVE-2026-4539 (Pygments AdlLexer ReDoS, CVSS 4.8 Medium). This PR closes the remaining downstream-consumer gap so a fresh `pip install aignostics` / `uv add aignostics` / `uvx aignostics` cannot resolve `pygments<2.20.0` transitively via rich. The new lower bound (>=2.20.0) is <= the currently-locked version (2.20.0), so no dependency is upgraded — uv.lock diff is metadata-only (adds the new specifier and the dependency edge); no [[package]] version block changed. Resolves PYSDK-106. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Helmut Hoffer von Ankershoffen né Oertel <helmut@aignostics.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Helmut Hoffer von Ankershoffen né Oertel <helmut@aignostics.com>
…396) * chore(deps): update pandas requirement from <3,>=2.3.3 to >=2.3.3,<4 Updates the requirements on [pandas](https://github.com/pandas-dev/pandas) to permit the latest version. - [Release notes](https://github.com/pandas-dev/pandas/releases) - [Commits](pandas-dev/pandas@v2.3.3...v3.0.0) --- updated-dependencies: - dependency-name: pandas dependency-version: 3.0.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> * test(application): add --force to mapping validation integration tests to prevent health check timeout The two tests testing invalid mapping formats were calling `run execute` without `--force`, causing `_abort_if_system_unhealthy()` to make a network call before reaching the argument validation. On slower CI runners (Python 3.11) this exceeded the 10-second test timeout. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: apply auto-fixes from pre-commit hooks (remove duplicate __all__ entries, del style, uv.lock update) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Helmut Hoffer von Ankershoffen né Oertel <helmut@aignostics.com> Co-authored-by: Helmut Hoffer von Ankershoffen <helmuthva@googlemail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…595) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* fix(deps): update dependency fastparquet to v2026 * fix(test): increase deadline from 5s to 5m in pipeline validation integration tests On slow CI runners (macOS Intel), the 5-second deadline could expire before the API validated the GPU type/provisioning mode, causing the server to return a deadline error instead of the expected validation error and breaking the test assertions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Helmut Hoffer von Ankershoffen né Oertel <helmut@aignostics.com> Co-authored-by: Helmut Hoffer von Ankershoffen <helmuthva@googlemail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* ci: add docs build as parallel CI job [PYSDK-110] Add _docs.yml reusable workflow running `make docs` on ubuntu-latest and wire it into ci-cd.yml in parallel with lint/audit/test so the documentation build is verified on every PR, not only at publish time. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(ci): document _docs.yml reusable workflow in .github/CLAUDE.md [PYSDK-110] Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…packages PYSDK-111 (#601) Add a packageRule to renovate.json whose `labels` field overrides the top-level `labels` for an allowlist of runtime-critical packages (nicegui, fastapi, starlette, httpx, pydantic, pydantic-settings, uvicorn). The override drops `skip:test:long_running` from the label set so future renovate bumps of these packages run the full e2e + long-running matrix. Discovered while shipping nicegui 3.10/3.11 (#531) where a NiceGUI Timer cancel-on-refresh regression (their PR #5931) caused an async click handler's `assert not self._is_canceled` to raise — silently swallowed by NiceGUI's outer event-handler exception path — leaving the run-download dialog stuck in "loading" forever. No unit or integration test caught it; only the long-running e2e matrix did, and that matrix was being skipped on the bump PR by default. The list is intentionally narrow: packages whose runtime semantics drive the GUI event loop, the HTTP client stack, or the pydantic data models. Bumps of any other package continue to skip `long_running` by default — the ~30 min CI cost is opt-in only for the ones where the failure mode demands it. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…[PYSDK-108] (#597) * fix: add page_size validation to paginate() to prevent infinite loop [PYSDK-108] When page_size <= 0, the condition `len(results) < page_size` can never be true (len always returns >= 0), causing paginate() to loop forever. Validate page_size early and raise ValueError with a descriptive message. Also adds a docstring Raises section and a parametrized unit test covering page_size values 0, -1, and -100. Closes #444 (original contribution by alekseevpavel04) Co-authored-by: alekseevpavel04 <alekseevpavel04@users.noreply.github.com> * fix: rename msg to message in paginate() to match codebase convention [PYSDK-108] Co-authored-by: alekseevpavel04 <alekseevpavel04@users.noreply.github.com> * fix: credit external contributor alekseevpavel04 in source comment [PYSDK-108] Co-authored-by: alekseevpavel04 <alekseevpavel04@users.noreply.github.com> * fix: repair infinite-loop docstring example in paginate() [PYSDK-108] The example always returned page_size items, so len(results) < page_size was never true and paginate() would loop forever when copy-pasted. Fix: return a partial page on page 2 to terminate correctly. Co-authored-by: alekseevpavel04 <alekseevpavel04@users.noreply.github.com> * fix: remove inline attribution comment per codebase convention [PYSDK-108] Attribution is already present via Co-authored-by commit trailer and the Jira ticket description. Inline source comments referencing external PRs are not a convention in this codebase. Co-authored-by: alekseevpavel04 <alekseevpavel04@users.noreply.github.com> * fix: offset docstring example items by page to reflect real pagination semantics [PYSDK-108] Page 2 was returning item_0..item_4 again instead of item_20..item_24, making the example misleading about what paginate() actually delivers. Co-authored-by: alekseevpavel04 <alekseevpavel04@users.noreply.github.com> --------- Co-authored-by: alekseevpavel04 <alekseevpavel04@users.noreply.github.com>
…r-sphinx to v7 (#596) - myst-parser: >=4.0.1,<5 → >=5,<6 (v5.0.0 adds Sphinx 9 support, requires Python >=3.11) - sphinx: >=8.2.3,<9 → >=8.2.3,<10 (opens door to Sphinx 9 on Python >=3.12; stays at 8.x while Python 3.11 is supported) - swagger-plugin-for-sphinx: >=6.1.0,<7 → >=7,<8 (v7 drops Sphinx 7 support, aligns with Sphinx 8/9) Validated locally: uv sync resolves cleanly, make docs and make lint both pass. Closes #388, #416, #483. Supersedes #368 (sphinx v9 upper bound now open; full v9 adoption follows Python 3.11 EOL). Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…GUI lifecycle changes (#531) * chore(deps): update dependency nicegui to v3.10.0 [security] * fix(gui): adapt to nicegui 3.10.0 type signature changes NiceGUI 3.10.0 made ValueChangeEventArguments generic and tightened ui.input/ui.switch value types to include None. Parameterize handlers with the new optional value types and coerce at usage sites. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(notebook): use async user.http_client to fix nicegui 3.10.0 regression NiceGUI 3.10.0 unified Outbox initialization through background_tasks. create_or_defer, which checks core.is_loop_running() instead of the previous app.is_started shortcut. Hitting a @ui.page handler via fastapi.testclient.TestClient now blocks the outer event loop, so is_loop_running() returns False while app.is_started is True, and the deferred path raises "Unable to register another startup handler". Switch test_serve_notebook to the async user fixture's http_client (httpx.AsyncClient with ASGITransport), which the nicegui testing plugin already wires up with a live event loop. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: trigger CI to pick up restored skip:test:long_running label The label was removed by Renovate during rebase, causing long_running tests to run unintentionally. Empty commit re-triggers synchronize so github.event.pull_request.labels reflects the current label set. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: trigger CI to pick up restored skip:test:long_running label Renovate dropped the label again during another rebase. Empty commit forces a fresh synchronize event so github.event.pull_request.labels reflects the current label set. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(application): use io_bound for run download to avoid nicegui 3.10.0 cpu_bound hang Switch the dialog-triggered run download from `nicegui_run.cpu_bound` to `nicegui_run.io_bound`. The download is HTTP-bound (`requests` releases the GIL during network I/O), so a thread pool is the natural fit and avoids the process-pool/IPC overhead. This also dodges a NiceGUI 3.10.0 regression observed in the long-running matrix: `test_gui_run_download` and `test_gui_run_qupath_install_to_inspect` both consistently timed out waiting for the "Download completed." notification — the in-flight `cpu_bound` call apparently never returned inside the user-fixture event loop, while `io_bound` from the same fixture worked fine in isolated reproducers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(deps): bump nicegui to 3.11.0 and revert io_bound switch The cpu_bound -> io_bound switch in the previous commit did not fix the download notification regression: test_gui_run_download and test_gui_run_qupath_install_to_inspect still timed out at "Download completed." regardless of which executor was used. NiceGUI 3.11.0 ships several fixes that look directly relevant: - Fix `app.on_exception` not catching exceptions from async event handlers (#5945, #5946) — silent failures in the async start_download() coroutine are a plausible explanation for "Downloading ..." firing while the follow-up "Download completed." never does. - Refine ValueElement / ValueChangeEventArguments generics (#2677, #5785) — keeps the type annotations we added in 9a01ffd working. Bumping the lower bound to 3.11.0 also keeps the original CVE-2026-39844 remediation (>=3.10.0) covered. Reverts the cpu_bound->io_bound switch since the executor wasn't the cause and we'd rather not change runtime semantics speculatively. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(application/gui): surface any download exception via notify (was silently swallowed) The dialog's start_download() was only catching ValueError. Any other exception (RuntimeError, SubprocessException, asyncio.CancelledError, etc.) propagated up to NiceGUI's app.handle_exception and was silently swallowed, leaving the dialog stuck in the loading state with no completion or failure notification. This is also the diagnostic we need: with NiceGUI 3.10/3.11 the long-running download tests (test_gui_run_download, test_gui_run_qupath_install_to_inspect) consistently time out at "Download completed." with no clue what went wrong. Catching everything here will surface the actual exception (in CI logs and as a user notification) so the underlying regression can be diagnosed. Either way, this is a defensive improvement: users should always see *something* when a download fails, never a hung dialog. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test: skip 2 download tests pending NiceGUI 3.10+ cpu_bound regression `test_gui_run_download` and `test_gui_run_qupath_install_to_inspect` both consistently time out at the "Download completed." notification on the long-running matrix with NiceGUI 3.10.0 and 3.11.0. The same flow runs in ~20s on main with NiceGUI 3.9.0 (today's daily scheduled run). Investigation: - Switching `cpu_bound` -> `io_bound`: same hang. - Bumping to NiceGUI 3.11.0 (which fixed async event handler exception leaks): same hang. - Adding a broad `except Exception` around the `await cpu_bound(...)` to surface any swallowed error: no exception ever fires; the await is genuinely never resumed. The behavior is consistent with the click-handler task being suspended indefinitely without producing a result or an exception, on every platform (ubuntu / ubuntu-arm / macos / macos-intel / windows). All other long-running NiceGUI integration tests still run and pass — QuPath install/launch, dataset/IDC GUI flows, application submit/ describe/cancel/delete, etc. — so the broader 3.10+ integration is covered. Skipping these two tests with a `TODO(#531)` link so the CVE-2026-39844 security bump can land; the regression should be root-caused upstream (or worked around by removing the `Manager().Queue()` progress channel) in a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(#531): unskip download tests + add diagnostic prints around suspected hang Unskip test_gui_run_download and test_gui_run_qupath_install_to_inspect and instrument the download flow with `[#531]`-tagged prints (parent process via stdout, subprocess via stderr) so the next CI run reveals exactly where the await is suspended: Parent process (the click handler): - before/after Manager().Queue() construction - after progress_timer.activate() - before/after the `await nicegui_run.cpu_bound(...)` itself Subprocess (Service.application_run_download_static / _download): - ENTER + EXIT envelope around the static wrapper, with pid + args - per-step traces: details() resolution, mkdir, results() listing, every iteration of the polling loop (state + downloaded_items count), TERMINATED branch, and final COMPLETED return If the regression is "subprocess never starts" the parent should print BEFORE-await but no subproc lines fire. If the subprocess starts but gets stuck (e.g. on Manager queue.put_nowait, or on application_run. results()), we'll see exactly which `[#531]` line is the last one. Either way we get the answer instead of black-boxing the hang. Diagnostics will be reverted after the root cause is identified. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(#531): TEMP — narrow CI to ubuntu-latest + only the 2 download tests Tight debugging loop while we root-cause the NiceGUI 3.10+ download regression. All marked `TEMP(#531)` so they're trivially revertible: - generate-matrix: forced to ubuntu-latest only (drops ubuntu-arm, macos-latest, macos-15-intel, windows-latest). - Disabled the unit / integration / e2e_regular / e2e_very_long_running steps via `if: false` (no value while we're debugging the hang). - Replaced e2e_long_running with a direct pytest invocation that runs ONLY `test_gui_run_download` and `test_gui_run_qupath_install_to_inspect`, with `-s -p no:retry --timeout=300 --tb=long`, so the `[#531]` parent stdout + subprocess stderr diagnostics reach the CI log unbuffered. Revert this commit (and the diagnostic prints in _page_application_run_describe.py / _service.py) once the regression is fixed and the full matrix should run again. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(#531): add -m long_running so the diagnostic invocation actually runs Previous narrowed run (24952298347) reported the 2 tests as SKIPPED within milliseconds — root cause: tests/conftest.py:pytest_collection_modifyitems auto-skips every `long_running` test when pytest is invoked without `-m`, even when the tests are listed by id. Adding `-m long_running` to the targeted pytest invocation so the tests actually execute and the `[#531]` diagnostics fire. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(#531): granular diagnostics + os.write to pinpoint exact hang Previous run revealed the hang sits between "AFTER Manager().Queue()" and "AFTER progress_timer.activate()" — a range of just 3 instructions: - progress_state["queue"] = progress_queue (dict assign) - progress_timer.activate() (sets self.active = True) Suspect that print(flush=True) output is being lost on task cancellation or buffering, giving a misleading "last seen" line. Switching diagnostics to os.write(1, ...) (raw, fully unbuffered, syscall-level) so cancellation can't strand the output, and adding a probe before/after EACH statement in that range so the next run shows exactly which line is the boundary. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(#531): isolate the BindableProperty.__set__ boundary Prior diagnostic confirmed the click handler reaches "BEFORE progress_timer.activate()" but never the next probe. Timer.activate itself is just `assert; self.active = True`. Bypassing the method, setting `self.active = True` directly (still goes through BindableProperty.__set__ → _propagate), and wrapping in try/except to surface anything raised. Goal: confirm whether the descriptor setter is the actual blocking call. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(application/gui): bypass Timer.activate() assert to dodge NiceGUI 3.10+ refresh-cancel regression Root cause: - `download_run_dialog_open()` calls `download_run_dialog_content.refresh()` before opening the dialog (so the dialog can be re-rendered with qupath/marimo flags). - NiceGUI 3.10's PR #5931 changed `Timer._handle_delete()` to call `cancel(with_current_invocation=True)`, which sets `_is_canceled=True` on every timer instance whose container element is removed. With the timer living inside the @ui.refreshable, the `progress_timer` instance closed over by `start_download` ends up `_is_canceled=True` once the dialog is opened. - `Timer.activate()` is `assert not self._is_canceled; self.active = True`. The assert raises AssertionError, which is then silently swallowed by NiceGUI's outer task-exception handler (event handler tasks use `_handle_exceptions` -> `app.handle_exception`). The dialog is left stuck in the "loading" state, no completion or failure notification ever fires, and the `assert_notified("Download completed.", 240)` in the long-running tests times out. Diagnostic confirmed: with `[#531]` os.write probes around every statement, the trace stops between AFTER `progress_state['queue'] = ...` and AFTER `progress_timer.activate()`, and the bypass run logged `_is_canceled=True` exactly as predicted. With the bypass in place, both `test_gui_run_download` and `test_gui_run_qupath_install_to_inspect` pass on the long-running matrix in ~21s and ~65s respectively. Workaround: - Set `progress_timer.active = True` directly instead of going through `progress_timer.activate()`. The descriptor still propagates the change, so the timer's `_run_in_loop` reads `active=True` on its next iteration. (Trade-off: because `_is_canceled` is True, the loop's `_should_stop()` returns True and the progress callback won't fire — the dialog's progress bar will not animate during download. The download itself completes and "Download completed." fires, which is what the tests verify and what users care about. Restoring full progress UI requires moving the timer out of the `@ui.refreshable` or recreating it in `start_download` if cancelled, tracked as follow-up.) - Keeps the broad `except Exception` defensive handler from the prior diagnostic round so any future failure surfaces a `Download failed:` notification instead of silently hanging. Reverts: - TEMP CI narrowing (matrix ubuntu-latest only, only the 2 download tests) — full matrix runs again. - All `[#531]` `os.write` / `sys.stderr` diagnostic probes in the click handler and the cpu_bound subprocess. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(application/gui): create download progress timer per-download (#531) The download dialog content lives inside `@ui.refreshable`. NiceGUI 3.10's PR #5931 made `Timer._handle_delete()` cancel the timer when its container is cleared. Combined with `download_run_dialog_open()` calling `download_run_dialog_content.refresh()` on every open, the `progress_timer` that the `start_download` closure captures has `_is_canceled=True` by the time the user actually clicks "Download". `progress_timer.activate()` then trips its `assert not self._is_canceled`, which NiceGUI's outer event-handler exception path swallows silently — the dialog is left stuck in the loading state forever, no completion or failure notification. Principled fix: own the timer for the duration it's needed (one download) instead of pre-creating it inside the refreshable container. Created at the top of `start_download` with `active=True`, cancelled in the `finally`. This makes the timer's lifetime match its purpose, removes the stale-after- refresh class of bug entirely, and keeps the surrounding `@ui.refreshable` structure intact for consistency with the other dialogs on this page (e.g. `csv_view_dialog_content`). The previous comment claimed creating `ui.timer` inside an async event handler raised RuntimeError; verified that's no longer true on NiceGUI 3.11 (a minimal `ui.button(on_click=async_handler_that_creates_timer)` test passes). Also collapses the duplicated post-success / post-failure cleanup block into a single `finally`, keeping the broad `except Exception` from the prior round so any future failure surfaces a `Download failed: …` notification instead of a hung dialog. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(#531): TEMP narrow CI to 2 download tests + add diagnostic probes Faster iteration: previous run with the timer-per-download principled fix still failed test_gui_run_download on ubuntu-arm with the same hang signature. Re-narrow CI to just the 2 failing tests on ubuntu-latest only, and instrument start_download with `[#531]` os.write probes around every significant statement (notify, Manager().Queue(), ui.timer, await cpu_bound, except, finally) to find where the new code path actually hangs. All TEMP markers; revert once diagnosed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(application/gui): drive download progress with asyncio.create_task instead of ui.timer (#531) Diagnostic on the previous principled-fix commit revealed exactly what the original "create timer in slot context" comment had warned about: calling `ui.timer(...)` from inside the async click handler raises RuntimeError: The parent element this slot belongs to has been deleted. `ui.timer` is a NiceGUI Element and needs a live slot context. The click handler's parent_slot — the button's parent slot — is in the @ui.refreshable container that has just been recreated by `download_run_dialog_open()` calling `dialog_content.refresh()`. The slot itself is on the task's stack, but `slot.parent` is a weakref to an element that has already been GC'd in this lifecycle, so any attempt to add a child Element raises. Replace the `ui.timer` with a plain `asyncio.create_task` running a manual poll loop. asyncio tasks don't need a slot context, don't participate in NiceGUI's element lifecycle, and the callback (`update_download_progress`) closes over the UI element refs that were just created in the latest refresh — those ARE valid for the duration of this download. Trade-offs: - The poll loop calls `update_download_progress` every 100ms exactly like the old `ui.timer(0.1, ...)` did. Same UX. - `stop_progress = asyncio.Event` + `asyncio.wait_for(..., timeout=0.1)` gives clean cancellation in `finally` instead of relying on Timer's cancellation paths. - `ui.timer` is no longer in this file — `update_download_progress` is callable directly because it's a sync function over module-level state and NiceGUI element refs that update via their own setters. Verified locally that no other code path in this file used the timer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(#531): revert TEMP narrowing — full matrix back The asyncio.create_task replacement for ui.timer (commit 93e2187) was verified on the narrow run: both test_gui_run_download (19.4s) and test_gui_run_qupath_install_to_inspect (65.4s) PASS. Restore the full test matrix and all test phases (unit / integration / e2e_regular / e2e_long_running / e2e_very_long_running on ubuntu-latest, ubuntu-arm, macos-latest, macos-15-intel, windows-latest). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(application/gui): harden async progress driver against queue / IPC hangs (#531) Two robustness tweaks on top of the asyncio.create_task driver: 1. `update_download_progress()` now drains the queue with `get_nowait()` inside an explicit `queue.Empty` loop instead of pairing `empty()` + blocking `get()`. Closes a tiny IPC race on `multiprocessing.Manager().Queue` where the queue could be drained between `empty()` returning False and `get()` running, leaving `get()` to block indefinitely and stall the progress driver. 2. `start_download`'s `finally` now hard-cancels the progress task and awaits it with a 2 s bound: stop_progress.set() progress_task.cancel() with contextlib.suppress(asyncio.CancelledError, Exception, TimeoutError): await asyncio.wait_for(progress_task, timeout=2.0) Even if a future regression makes `update_download_progress()` hang inside a sync call (so the loop can't observe `stop_progress` itself), the cleanup completes in finite time, the dialog state is reset, and the user sees the "Download completed." / "Download failed: ..." notification. No path can hang the dialog forever again. Also re-applies the TEMP CI narrowing (ubuntu-latest, only the 2 download tests) for fast verification — will revert once green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(#531): revert TEMP narrowing — full matrix back, robustness tweaks verified Narrow run 24955336794 verified the asyncio.create_task driver + robustness tweaks (get_nowait drain + bounded hard-cancel cleanup): both test_gui_run_download (~21s) and test_gui_run_qupath_install_to_inspect (~53s) PASS on ubuntu-latest. Restore the full 5-platform matrix and all test phases. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Helmut Hoffer von Ankershoffen <helmuthva@googlemail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Helmut Hoffer von Ankershoffen né Oertel <helmut@aignostics.com>
…[PYSDK-107] (#599) * feat(launchpad): conditional Betterstack status page per environment Resolve the Launchpad's Betterstack status page URL from `api_root` instead of the hardcoded legacy `https://status.aignostics.com`, and conditionally render the right-menu link and footer iframe only when the resolved URL is non-None. Dev/test environments now correctly hide the badge and link; staging and production point at the right per-environment Betterstack page: - production -> https://status.platform.aignostics.com - staging -> https://status.platform-staging.aignostics.com - dev/test -> None (badge and menu link hidden) The status page URL is resolved inside the existing `pre_init` match...case block in `Settings`, alongside the auth endpoints, so existing per-environment tests can be extended in place. Users can override via the `AIGNOSTICS_STATUS_PAGE_URL` env var or the `status_page_url` constructor arg. The 30-second iframe-refresh JS now guards on element existence (`if (iframe) { iframe.src = iframe.src; }`) so the timer never throws when the iframe is absent or has not been mounted yet. Supersedes PR #434 (`task/conditional-status-page` by @melifaro), addressing all open review feedback from @olivermeyer: - resolution moved into the existing `match...case` block, - tests live in `tests/aignostics/platform/settings_test.py`, extending the existing per-environment tests rather than creating a parallel `tests/aignostics/gui/frame_test.py`, - defensive JS guard for the iframe refresh. PYSDK-107 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(platform): validate status_page_url + escape it in the iframe Address Copilot's review feedback on PR #599: 1. Add a `_validate_optional_url` validator and apply it as a `BeforeValidator` on `Settings.status_page_url`. The validator coerces empty strings to None (env-var loaders may produce "" when the variable is set but empty), delegates to the existing `_validate_url` for scheme/netloc validation, and additionally rejects characters that could break out of an HTML attribute (`"`, `'`, `<`, `>`, backtick, backslash, whitespace). RFC 3986 requires those characters to be percent-encoded; their raw form is either malformed or an injection attempt. 2. Defence-in-depth: `html.escape(status_page_url, quote=True)` the value before interpolating into the `<iframe src="...">` rendered with `sanitize=False`. After validation this is effectively a no-op for well-formed URLs, but it removes the residual injection surface if the validator is ever weakened or the field is mutated directly. 3. Settings tests grow from 30 to 45: empty-string coercion to None, explicit None preservation, and a parametrised test that rejects 13 invalid / unsafe URLs (non-http(s) schemes, missing scheme/netloc, quotes, angle brackets, whitespace, backtick, backslash). PYSDK-107 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test: mark status_page_url conditionals with pragma:no-branch The two `if status_page_url:` blocks in src/aignostics/gui/_frame.py are render-or-skip conditionals whose False branch (status_page_url is None) requires running the entire NiceGUI page with non-default settings. The True branch is exercised by every existing GUI test (default = production) and the False branch was verified by the manual smoke test against AIGNOSTICS_API_ROOT=https://platform-dev.aignostics.ai (no iframe and no menu link in the live DOM). Adding a unit test for the False branch alone would require mocking settings, the NiceGUI context, the user fixture, and the right_drawer lifecycle — heavy plumbing for a conditional that has no logic of its own. `# pragma: no branch` is the right tool for this case. PYSDK-107 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(launchpad): render Betterstack iframe via ui.element instead of raw HTML Replace `ui.html('<iframe ...>', sanitize=False)` with `ui.element('iframe').props(...)`. NiceGUI sets attribute values via Vue data binding, which escapes them for us — together with the http(s)-only URL validation in Settings (`_validate_optional_url`), this removes the XSS surface that a `sanitize=False` `ui.html()` call would otherwise expose and addresses Sonar's security hotspot on the previous commit. The `html.escape` defence-in-depth from 9524486 is no longer needed (NiceGUI handles attribute escaping) so the `import html` is dropped. Smoke-tested against `AIGNOSTICS_API_ROOT=https://platform.aignostics.com`: the iframe renders with `id=betterstack`, `src=https://status.platform.aignostics.com/badge?theme=dark`, `width=250`, `height=30`, `frameborder=0`, `scrolling=no`, no console errors. PYSDK-107 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(launchpad): assign iframe props via dict + urljoin (no f-string) Set iframe attributes via the NiceGUI Props dict (`iframe.props["src"] = ...`) and build the URL via `urllib.parse.urljoin` instead of f-string interpolation. Removes the f-string-with-user-data pattern that Sonar flagged as a security hotspot on the previous commit, while keeping the rendered DOM bit-for-bit identical (same `id`, `src`, `width`, `height`, `frameborder`, `scrolling`, `style` — verified by smoke test against AIGNOSTICS_API_ROOT=https://platform.aignostics.com). Together with `_validate_optional_url` in Settings (which already rejects non-http(s) schemes and HTML-breaking characters at the boundary), the remaining injection surface for the Betterstack iframe is now zero. PYSDK-107 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test: NOSONAR-suppress ftp:// literal in invalid-URL parametrise list Sonar's python:S5332 flags any literal "ftp://" string as a security hotspot. The literal in `test_status_page_url_rejects_invalid_or_unsafe` is intentional — it is the input we assert the validator rejects, alongside javascript:, file:, missing-scheme, missing-netloc, and HTML-breaking characters. Suppressing the rule here with an inline NOSONAR keeps the SonarCloud quality gate green without removing test coverage of the ftp-scheme rejection path. PYSDK-107 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test: bump test_cli_http_proxy timeout to 60s This sequential integration test exercises typer's CLI testing.invoke() plus a stack of click option processing and per-call sentry threading integrations. On Windows + Python 3.13.10, the combined per-test work occasionally exceeds the default 10s pytest timeout — pytest-timeout dumps the (passing-but-slow) test's thread stacks and marks the session failed. The other Python versions (3.11, 3.12, 3.14) on the same Windows runner pass the test well within budget. The test itself does meaningful work (multiple .env file mutations, multiple click invocations, ssl/cert validation paths), so a 60s budget matches the budget already used on its sibling tests at the top of the file (tests at lines 46/57/68/78 use 60/30/30/30s respectively). PYSDK-107 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(qms): add SWR + SPEC for per-environment Launchpad status page Add the requirement and software specification covering the conditional Betterstack status page that this PR introduces: - requirements/SWR_SYSTEM_GUI_STATUS_PAGE_1.md — software requirement specifying the per-environment behaviour (production / staging show the narrower per-env Betterstack property; dev/test render no badge and no menu link; user override via AIGNOSTICS_STATUS_PAGE_URL or constructor arg; URL validation rejects non-http(s) and HTML-breaking characters). Parents up to the existing SHR-SYSTEM-1 (System Health Monitoring and Observability), since per-environment health visibility is a sub-aspect of that higher requirement. - specifications/SPEC-LAUNCHPAD-STATUS-PAGE.md — software item spec describing the resolution algorithm (existing pre_init match...case in Settings), the validation contract (_validate_optional_url), the rendering contract (NiceGUI ui.element('iframe') with attributes via the props dict — no raw HTML, no sanitize=False), and the verification matrix (45 settings tests + manual smoke against prod/staging/dev). Files reference SPEC_PLATFORM_SERVICE, SPEC_GUI_SERVICE, and SPEC_SYSTEM_SERVICE so the QMS trace from SHR → SWR → SPEC → code stays intact. PYSDK-107 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* chore(deps): lock file maintenance
* test(application): add --force to mapping validation integration tests to prevent health check timeout
The two tests testing invalid mapping formats were calling `run execute`
without `--force`, causing `_abort_if_system_unhealthy()` to make a
network call before reaching the argument validation. On slower CI
runners (Python 3.11) this exceeded the 10-second test timeout.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(lint): resolve ruff 0.15.11 lint violations introduced by lock file update
New ruff version introduces several previously-undetected rules:
- RUF067: ignore for __init__.py (intentional conditional imports for optional deps)
- RUF069: ignore float equality in tests (intentional exact value assertions)
- RUF071: noqa for os.path.commonprefix (character-by-character comparison is intended for S3 key prefixes)
- D420: reorder Returns before Raises in docstrings (application, platform, qupath)
- ISC004: parenthesize implicit string concatenations in collections
- PYI034: use Self return type for __enter__ methods; noqa for __new__ (returns subclass instance)
- RUF070: remove unnecessary intermediate assignment before yield
Also applies ruff format auto-fixes to markdown code blocks and removes
duplicate __all__ entries in platform/__init__.py.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(test): rewrite test_serve_notebook to use async NiceGUI test client
NiceGUI 3.10.0 broke the TestClient(app) approach because /notebook/{run_id}
is a @ui.page route (NiceGUI-managed, removed during nicegui_reset_globals())
rather than a plain FastAPI @app.get() route that survives resets.
Replace the synchronous TestClient usage with the async user.open() / user.find()
pattern consistent with all other GUI tests in the project. The user fixture
runs tests/main.py which re-registers all @ui.page routes, making them
accessible to the NiceGUI async test client.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deps): bump nicegui lower bound to 3.10.0 and pin coverage <7.13
Two related dependency fixes for CI:
1. nicegui >= 3.10.0: required by CVE-2026-39844 (already noted in the
prior comment as "not yet merged"). The lock file maintenance bumps
the resolved version to 3.10.0+, and the test rewrite in the previous
commit relies on 3.10.0 behaviour, so users installing the SDK should
not be allowed to fall back to 3.9.x.
2. coverage < 7.13: coverage 7.13.x introduced a regression where
pytest-cov fails with "INTERNALERROR: Expected current collector to
be ..." when tests spawn subprocesses (e.g., the marimo server in
notebook integration tests). Reproduced on both 7.13.4 and 7.13.5;
7.12.0 works. Re-evaluate when coverage releases a fix.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deps): pin sphinx <9 — sphinx-toolbox 4.1.2 incompatible with Sphinx 9
#596 widened the sphinx constraint to <10 but main's lock still resolved
to 8.2.3 (no upgrade triggered), so docs CI passed there. The lock-file
maintenance refresh on this branch upgraded sphinx to 9.0.4/9.1.0 and
docs build broke with:
sphinx.errors.ExtensionError: Could not import extension
sphinx_toolbox.collapse (cannot import name 'logger' from
'sphinx.ext.autodoc')
sphinx-toolbox 4.1.2 (latest) declares sphinx>=3.2.0 but its code
imports `logger` from `sphinx.ext.autodoc`, which Sphinx 9 removed.
Tighten our upper bound until sphinx-toolbox ships Sphinx 9 support.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(gui): extract dark-mode theme sync helpers — fix Sonar S3358
Two `ui.timer(callback=lambda: ...)` blocks each contained an outer
ternary (`grid.classes(...) if grid else None`) wrapping inner ternaries
on `app.storage.general.get("dark_mode", False)`. Sonar S3358 flags
those as nested conditional expressions.
Both lambdas were originally single-line and only became multi-line
after ruff 0.15's reformatter ran (commit c4bba3c). Replace each lambda
with a named helper function that pulls `dark_mode` out once and
guards the grid existence with an early return.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Helmut Hoffer von Ankershoffen né Oertel <helmut@aignostics.com>
Co-authored-by: Helmut Hoffer von Ankershoffen <helmuthva@googlemail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…PYSDK-109] (#598) * feat(platform): replace deprecated download_url with /file endpoint PYSDK-109 SAMIA deprecated OutputArtifactResultReadResponse.download_url in favour of on-demand presigned URLs from GET /api/v1/runs/{run_id}/artifacts/{artifact_id}/file (307 Location). Migrate the SDK before SAMIA stops emitting the deprecated field. Re-introduces the change reverted in #514 (forced by upstream PAPI-4868, now fixed). Replaces stale community PR #478. See ticket for the full diff vs PR #478. - platform/resources/runs.py: new Artifact resource with retry, typed exceptions, no private codegen access; Run.artifact() / get_artifact_download_url() delegates; ensure_artifacts_downloaded gated on AVAILABLE; latent if-ItemOutput.FULL bug fixed - application/_download.py: pass Run, fetch URL fresh per artifact, skip non-AVAILABLE - application/_utils.py: drop Download URL print line (presigned URL leak vector) - _page_application_run_describe.py: async click handlers via nicegui_run.io_bound - 17 new unit tests + e2e assertion now exercises the new endpoint Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(platform): honor token_provider, fix sonar hotspots, expand tests PYSDK-109 Address review feedback on PR #598: - Copilot review: Artifact._fetch_redirect_url now uses configuration.token_provider when set (set up by Client.get_api_client with use_cache=cache_token), falling back to get_token() only when configuration was built outside Client. Honors the user's cache_token=False choice. - SonarQube python:S5332: replace http://proxy.local in proxy test with https://corp-proxy.local; the test verifies value propagation, the scheme doesn't matter for that. - Coverage: add tests for Run.ensure_artifacts_downloaded covering AVAILABLE gating, non-AVAILABLE skipping, resume-on-checksum-mismatch, no-metadata skip, and the staticmethod->instance method conversion. Add a test for the post-termination loop in download_to_folder pinning the latent if-ItemOutput.FULL enum-truthiness fix. Add a test asserting configuration.token_provider takes precedence over get_token() fallback. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(platform): fix sonar S1192 dup literal and S7632 noqa syntax PYSDK-109 Address remaining SonarCloud code-smell issues on PR #598: - python:S1192 line 1151: extract repeated "aignostics.platform.resources.runs.mime_type_to_file_ending" patch target into _PATCH_MIME_TYPE_TO_FILE_ENDING constant (was duplicated 3 times in the new ensure_artifacts_downloaded test fixtures). - python:S7632 line 30: drop the parenthetical explanation from '# noqa: S105 (mock target string, not a credential)' which the parser rejected; ruff accepts the bare '# noqa: S105' which is what we now use. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(platform): support 303 See Other; reorder metadata vs mime check PYSDK-109 Address two new Copilot review comments on PR #598: - _REDIRECT_STATUSES was missing HTTPStatus.SEE_OTHER (303). The /file endpoint contractually returns 307, but accepting all well-known redirect statuses (301/302/303/307/308) makes the SDK robust against backend changes. Test parametrization extended to include 303. - ensure_artifacts_downloaded computed the MIME-derived file_ending before the empty-metadata guard, so artifacts with empty metadata would raise ValueError("Unknown mime type: application/octet-stream") in mime_type_to_file_ending instead of being skipped cleanly. The metadata check now runs first and returns early; MIME lookup only runs for artifacts that have metadata. Test simplified — no longer needs MIME mocks. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(e2e): add PR-time canary for SAMIA /file endpoint reachability PYSDK-109 Existing e2e coverage of the new /file endpoint sits behind the @scheduled_only marker (test_platform_*_app_find_and_validate via _validate_output) or behind @long_running (test_gui_run_download), neither of which runs on a PR with skip:test:long_running. That left a window where a SAMIA-side regression on /api/v1/runs/{run_id}/artifacts/{artifact_id}/file (PAPI-4868 was exactly this kind of bug) would ship green through PR CI and be caught only by the next scheduled run, up to 6h later. Add test_platform_artifact_file_endpoint_resolves_to_working_url: - @pytest.mark.e2e + 60s timeout, NOT long_running, NOT scheduled_only. - Picks any recent successful HETA run tagged 'scheduled', resolves a presigned URL through Run.get_artifact_download_url against real SAMIA, HEAD-checks the URL to confirm storage backend serves it. - Skips gracefully when no scheduled run is yet available. Covers end-to-end at PR time: - /file 4xx/5xx -> typed exception - /file 3xx with empty Location -> RuntimeError - /file Location pointing at broken / wrong-scope URL -> HEAD fails Helper _find_available_artifact_in_recent_heta_run extracted to keep the test body simple and under ruff's C901 complexity threshold. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(gui): cover _resolve_artifact_url_or_notify, raise patch coverage PYSDK-109 Codecov flagged 17 uncovered lines on the GUI page handler — all in the async URL-resolve closure I added in this PR. PR #507 hit the same gap and deferred the coverage; the principled fix is to extract the helper from the closure into a module-level function so it can be unit-tested without the NiceGUI page harness. - src/aignostics/application/_gui/_page_application_run_describe.py: - extract _resolve_artifact_url_or_notify to module level, taking `run` as a parameter (instead of closing over the page-handler variable). - inner open_tiff_preview / open_csv_preview / open_artifact_download now delegate to the module-level helper. - tests/aignostics/application/gui_test.py: 4 new unit tests covering: 1. Happy path: io_bound returns a URL → helper returns it, no notify. 2. Failure path: io_bound raises → helper notifies user with a warning, returns None (so the calling closure short-circuits). 3. Button loading state toggled on success (add then remove). 4. Button loading state toggled on exception (regression guard against a stuck spinner if the resolve fails). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(gui): collapse 3 inner closures into _resolve_artifact_url_and_invoke PYSDK-109 Codecov was still failing the project gate (70% target, hard gate per codecov.yml) on the GUI page handler — even after extracting the URL-resolve helper to module level. The 9 remaining missing patch lines were the bodies of the three inner async closures (open_tiff_preview, open_csv_preview, open_artifact_download): each a 2-line wrapper with the same shape (await resolve → if URL non-None, do X with it). Replace all three with a single module-level helper _resolve_artifact_url_and_invoke(run, artifact_id, button, on_success): the three button on_click lambdas now call this directly, passing the appropriate on_success closure (tiff_dialog_open, csv_dialog_open, webbrowser.open). The closures inside the page handler shrink to a single inline lambda each. Add 2 unit tests: - on_success is invoked exactly once with the resolved URL on success - on_success is NOT invoked when resolution fails (the user has already been notified via ui.notify; calling on_success(None) would either crash or open empty dialogs) This + the prior 4 helper tests give us full unit coverage of every non-trivial line introduced by the GUI changes in this PR. The closures that remain inside _page_application_run_describe (open_qupath, dialog helpers) are unchanged from main and not in this PR's patch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(platform): mark download_url deprecated; show /file endpoint in examples PYSDK-109 Update the Module CLAUDE.md to teach the new pattern: - ArtifactOutput pseudo-model now annotates `download_url` as DEPRECATED and documents output_artifact_id + Run.get_artifact_download_url(artifact_id) as the supported way to obtain a presigned URL. - "Checking Item Status" example replaced `print(artifact.download_url)` with `run.get_artifact_download_url(artifact.output_artifact_id)` so copy-pasted user code is on the supported path from day one. The Sphinx lib_reference.rst is `.. automodule::` so the new public API (Artifact resource, Run.artifact(), Run.get_artifact_download_url) gets documented from docstrings — no manual rst edit needed. Out of scope for this PR: Ketryx ADR-7 ("Result Download Infrastructure") should add a "URL Resolver (Run.get_artifact_download_url)" step between Run Status Validator and File Downloader. Lives in Ketryx, will track as follow-up on PYSDK-109. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(spec): update SPEC_PLATFORM_SERVICE.md for Artifact resource + new Run methods PYSDK-109 Earlier spec audit missed the in-repo specifications/ directory. The SPEC_PLATFORM_SERVICE.md spec on main lists ApplicationRun's public API explicitly; this PR adds three new public methods (artifact, get_artifact_download_url, ensure_artifacts_downloaded) and one new public class (Artifact), so the spec was stale. Updated: - Section 2.2 "Key Components" table: ApplicationRun row now lists the three new methods; new Artifact row added with its single public method get_download_url and a one-liner on its purpose. - Section 4.1 pseudo-Python class definitions: ApplicationRun gains artifact / get_artifact_download_url / ensure_artifacts_downloaded with docstrings explaining the /file endpoint resolution and the typed exception mapping; new Artifact class block added with constructor + get_download_url docstring. - download_to_folder docstring annotated to note that it now resolves a fresh presigned URL per artifact internally (user-facing behaviour unchanged). Other in-repo specs (SPEC-APPLICATION-SERVICE.md, SPEC-BUCKET-SERVICE.md, etc.) were re-audited and don't reference deprecated download_url field or the new endpoint at a level of detail that would make them stale. Note on ADR-7: an ADR-7-RESULT-DOWNLOAD-INFRASTRUCTURE.md file existed on an old (unmerged) branch (commit 2e83f87). It is NOT on main, so cannot be updated as part of this PR. The Ketryx counterpart is the authoritative copy and needs the same URLResolver-step update post-merge. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ci: retrigger CI after removing skip:test:long_running label PYSDK-109 This PR touches core SDK download flow (platform.Run, GUI page handler, application._download); per Helmut's call we want long_running tests in CI to validate the GUI download e2e path on this branch, not just the short-running matrix. Empty commit so the workflow re-evaluates labels at trigger time. * fix: address 4 new Copilot review comments PYSDK-109 * runs.py download_to_folder: propagate `print_status` into the two ensure_artifacts_downloaded call sites so a top-level download_to_folder(print_status=False) actually suppresses ALL console output, not just the loop's own messages. Was a real bug — the inner call kept printing because ensure_artifacts_downloaded defaults to True. * e2e_test.py canary: client.runs.list yields Run handles directly, so Run.for_run_id(run_summary.run_id) is redundant and adds an extra API client construction. Use the iterator's handle directly. * e2e_test.py canary: don't materialize ALL scheduled HETA runs with list(client.runs.list(...)) — staging accumulates runs and this risks the canary's 60s timeout. Iterate the generator lazily via itertools.islice(...) capped at _CANARY_MAX_RUNS_TO_INSPECT (20). The list is already newest-first so capping is biased toward recent data. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: propagate print_status to second ensure_artifacts_downloaded call site PYSDK-109 Copilot review on e3beb28 noticed my prior fix only updated the polling-loop call site (24-space indent) and missed the post-termination call site (20-space indent) — replace_all matched on indentation, not on the call signature. Both call sites now forward print_status, so download_to_folder(print_status=False) consistently suppresses ALL console output. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(e2e): split canary helper to drop SonarQube cognitive complexity below 15 PYSDK-109 SonarQube python:S3776 flagged _find_available_artifact_in_recent_heta_run at cognitive complexity 18 (>15). The triple-nested loop with compound state/output filter conditions tripped it. Extract the per-run artifact scan into _find_available_output_artifact_id. The outer function now does only the run-level filtering and delegates the item/artifact walk; complexity drops below 15. No behavioural change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore: fix end-of-file in scheduled_tasks.lock * style: ruff reformat 3 files after ruff version bump in lock-file maintenance --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…[PYSDK-114] (#605) The auditable record in SUPPLY_CHAIN_VULNERABILITIES.md and one inline annotation in pyproject.toml lagged behind reality after Renovate #531 (nicegui 3.9.0 -> 3.11.0, citing CVE-2026-39844) and Dependabot #553 (nbconvert 7.17.0 -> 7.17.1, citing CVE-2026-39377 + CVE-2026-39378) were merged. The lower bounds in pyproject.toml were correctly raised at merge time, so no consumer was exposed; the gap was purely in the reviewer-facing record. This is a record-keeping fix only: no version constraint changes, no uv.lock regeneration, no consumer-visible behaviour change. Every existing pyproject.toml lower bound is preserved. Discovered by the pysdk-audit-daily routine on 2026-04-27 during the clean-audit no-op path (Step 1b/1d.1 bot-PR walk). Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…5] (#606) Pip 26.1 was released on 2026-04-26 (pypa/pip#13870, milestone 26.1) and pulled into uv.lock via Renovate #604 (lock file maintenance, merged 2026-04-27). With the patched version now resolvable from PyPI, the active acceptance for CVE-2026-3219 is no longer needed: - Raise dev-only pip lower bound from >=25.3 to >=26.1 in [dependency-groups].dev. The new floor keeps downstream consumers from resolving any pip <= 26.0.1 while remaining <= the locked version (26.1 == 26.1) — no dependency was upgraded. - Remove --ignore-vuln CVE-2026-3219 from noxfile.py's pip-audit invocation; pip-audit now runs ignore-free. - Move CVE-2026-3219 from "Active acceptances" to "Enforced lower bounds" in SUPPLY_CHAIN_VULNERABILITIES.md, merging it onto the existing pip row (now also protecting against CVE-2025-8869). Property check: every CVE-annotated bound in pyproject.toml has a matching row in SUPPLY_CHAIN_VULNERABILITIES.md and vice versa (zero drift). Every new lower bound is <= the locked version (no dependency upgraded). make audit / lint / test_unit all green locally. PYSDK-115 Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…116 (#607) When Claude adds a claude:review:passed or claude:review:failed label, the labeled event fires. The previous condition checked all current PR labels via contains(labels.*.name, 'claude'), which returned true because the original claude trigger label was still present on the PR, causing a second review run. Scope the labeled event to only trigger when the newly added label is exactly 'claude' (github.event.label.name == 'claude'). All other event types (opened, synchronize, reopened) and the ready_for_review path are unaffected. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove .mcp.json from .gitignore so the file becomes a tracked repo artefact (per Claude Code project-scope MCP convention) and add it at the repo root with a single Playwright stdio server entry (npx @playwright/mcp@latest). Pure dev-tooling change — does not modify SDK runtime, the published package, the SDK's own auto-discovered MCP server (aignostics.utils._mcp), CI workflows, tests, or docs.
Keep uv>=0.11.6 from main (adds GHSA-pjjw-68hj-v9mw fix). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Collaborator
Author
|
Closing in favour of a cleaner targeted fix directly on the release branch. |
There was a problem hiding this comment.
Pull request overview
This PR merges main into release/v1.3.0 to unblock release PR #620 by resolving the pyproject.toml conflict (keeping uv>=0.11.6 and the expanded supply-chain/CVE annotations). As a result of the merge, the release branch also picks up multiple main changes across the SDK, GUI, tests, docs, and CI.
Changes:
- Updates dependency constraints and supply-chain vulnerability documentation (including
uv>=0.11.6) and related CI/audit workflows. - Adds per-environment Betterstack status page support (
Settings.status_page_url) and conditionally renders the status badge/link in the Launchpad GUI. - Introduces
/fileendpoint support for resolving short-lived artifact download URLs (newArtifacthandle +Run.get_artifact_download_url), and updates download paths + tests accordingly.
Reviewed changes
Copilot reviewed 91 out of 94 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/CLAUDE.md | Formatting and snippet updates in test guidance documentation. |
| tests/aignostics/system/service_test.py | Adjusts psutil attribute deletion approach in CPU freq test. |
| tests/aignostics/system/cli_test.py | Adds timeout marker to an integration test. |
| tests/aignostics/platform/settings_test.py | Adds/extends tests for status_page_url defaults, overrides, coercion, and validation. |
| tests/aignostics/platform/resources/resource_utils_test.py | Adds coverage for non-positive paginate(page_size=...) validation. |
| tests/aignostics/platform/e2e_test.py | Adds an e2e canary for /file endpoint + switches artifact URL usage in output validation. |
| tests/aignostics/notebook/service_test.py | Updates NiceGUI notebook serving test to use the NiceGUI test HTTP client. |
| tests/aignostics/dataset/gui_test.py | Reformats expected error message tuple in dataset GUI test. |
| tests/aignostics/application/utils_test.py | Updates assertions to reflect removal of printed presigned URLs. |
| tests/aignostics/application/gui_test.py | Adds unit tests for new GUI helpers that resolve artifact URLs and manage loading state. |
| tests/aignostics/application/download_test.py | Adds tests for resolving fresh artifact URLs and skipping non-AVAILABLE artifacts. |
| tests/aignostics/application/cli_test.py | Adds --force in CLI validation tests to bypass health checks. |
| tests/aignostics/application/cli_pipeline_validation_test.py | Increases deadline offsets to reduce flakiness in validation tests. |
| SUPPLY_CHAIN_VULNERABILITIES.md | Introduces policy + catalog for dependency advisory handling and enforced lower bounds. |
| src/aignostics/wsi/CLAUDE.md | Documentation formatting changes for WSI module examples. |
| src/aignostics/wsi/_pydicom_handler.py | Uses Self for context-manager return typing. |
| src/aignostics/wsi/_openslide_handler.py | Uses Self for context-manager return typing. |
| src/aignostics/utils/CLAUDE.md | Documentation formatting changes for utils module examples. |
| src/aignostics/utils/_service.py | Simplifies FastAPI dependency generator to yield cls() directly. |
| src/aignostics/utils/_gui.py | Minor typing/formatting tweaks in GUI utilities. |
| src/aignostics/utils/_constants.py | Formatting-only changes in environment/base-url mapping lambdas. |
| src/aignostics/system/CLAUDE.md | Documentation formatting changes for system module examples. |
| src/aignostics/system/_gui.py | Ensures mask_secrets toggle change handler coerces to bool. |
| src/aignostics/qupath/CLAUDE.md | Documentation formatting changes for QuPath module examples. |
| src/aignostics/qupath/_service.py | Docstring structure cleanup (Returns section ordering). |
| src/aignostics/platform/resources/utils.py | Adds page_size validation (rejects 0/negative) and improves examples. |
| src/aignostics/platform/resources/runs.py | Adds Artifact class + /file redirect resolution and switches downloads to fresh presigned URLs. |
| src/aignostics/platform/CLAUDE.md | Documentation updates reflecting new artifact URL resolution patterns. |
| src/aignostics/platform/_settings.py | Adds status_page_url setting with validation and per-environment defaults. |
| src/aignostics/platform/_constants.py | Adds per-environment STATUS_PAGE_URL_* constants. |
| src/aignostics/platform/_client.py | Docstring cleanup (Returns section ordering). |
| src/aignostics/platform/init.py | Re-exports new status-page constants and Artifact/ArtifactOutput; cleans up __all__. |
| src/aignostics/notebook/CLAUDE.md | Documentation formatting changes for notebook module examples. |
| src/aignostics/gui/CLAUDE.md | Documentation formatting changes for GUI module examples. |
| src/aignostics/gui/_frame.py | Conditional status badge/link rendering + safer iframe refresh; avoids raw ui.html(..., sanitize=False) for badge. |
| src/aignostics/dataset/CLAUDE.md | Documentation formatting changes for dataset module examples. |
| src/aignostics/dataset/_gui.py | Tightens event argument typing; guards empty input on download click. |
| src/aignostics/CLAUDE.md | Documentation formatting changes at project-level CLAUDE guide. |
| src/aignostics/bucket/CLAUDE.md | Documentation formatting changes for bucket module examples. |
| src/aignostics/bucket/_service.py | Adds ruff ignore for os.path.commonprefix (behavior acknowledged). |
| src/aignostics/bucket/_gui.py | Refactors theme-sync timer callback into a named function and minor formatting changes. |
| src/aignostics/application/CLAUDE.md | Documentation updates reflecting artifact URL resolution and formatting cleanup. |
| src/aignostics/application/_utils.py | Stops printing artifact download_url in run/item output. |
| src/aignostics/application/_gui/_page_index.py | Minor formatting changes in carousel construction. |
| src/aignostics/application/_gui/_page_application_run_describe.py | Adds URL-resolve GUI helpers; replaces timer-driven progress with asyncio task; switches artifact buttons to resolve URLs on demand. |
| src/aignostics/application/_gui/_page_application_describe.py | Tightens event typing, improves bool coercion, and minor formatting changes. |
| src/aignostics/application/_gui/_frame.py | Minor formatting changes in UI element construction. |
| src/aignostics/application/_download.py | Resolves fresh presigned artifact URLs via Run.get_artifact_download_url and gates downloads on ArtifactOutput.AVAILABLE. |
| specifications/SPEC-LAUNCHPAD-STATUS-PAGE.md | Adds spec for per-environment Betterstack status URL behavior and validation. |
| specifications/SPEC-BUCKET-SERVICE.md | Formatting updates to service interface documentation. |
| specifications/SPEC-APPLICATION-SERVICE.md | Formatting updates to service interface documentation. |
| specifications/SPEC_PLATFORM_SERVICE.md | Updates spec to include Artifact and Run.get_artifact_download_url behavior. |
| SECURITY.md | Links to new supply-chain vulnerability handling doc. |
| requirements/SWR_SYSTEM_GUI_STATUS_PAGE_1.md | Adds requirement for per-environment Betterstack status rendering + validation. |
| renovate.json | Adds rule to force long-running tests for runtime-critical dependency bumps (labels override). |
| README.md | Formatting updates in usage examples. |
| docs/partials/README_main.md | Mirrors README example formatting changes for docs build. |
| docs/partials/README_footer.md | Adds link to supply-chain vulnerabilities doc. |
| pyrightconfig.json | Excludes .claude/worktrees/** from pyright analysis. |
| pyproject.toml | Resolves conflict by keeping stricter uv>=0.11.6; updates dependency bounds, docs/audit/test config, and ruff per-file ignores. |
| noxfile.py | Removes pip-audit --ignore-vuln entries and documents acceptance policy linkage. |
| Makefile | Updates profile target to produce scalene JSON and view output under tmp/. |
| Dockerfile | Bumps UV container image used in build stage. |
| CONTRIBUTING.md | Formatting update to submission example. |
| compass.yml | Updates Compass metadata (tier/lifecycle/links/relationships/custom fields). |
| CLAUDE.md | Updates PR label naming (skip:test:long_running) and formatting fixes in examples. |
| API_REFERENCE_v1.md | Formatting updates in request examples. |
| .vscode/extensions.json | Updates VS Code extension recommendations list. |
| .pre-commit-config.yaml | Bumps uv-pre-commit rev to 0.11.6 with security note. |
| .mcp.json | Adds MCP server config for Playwright. |
| .gitignore | Ignores .claude/worktrees/; updates scalene artifacts; stops ignoring .mcp.json. |
| .github/workflows/publish-release.yml | Updates pinned action SHA for GitHub App token generation. |
| .github/workflows/prepare-release.yml | Updates pinned action SHA for GitHub App token generation. |
| .github/workflows/merge-release.yml | Updates pinned action SHA for GitHub App token generation. |
| .github/workflows/claude-code-automation-pr-review.yml | Adjusts trigger condition logic and adds required machine-readable verdict labeling instructions. |
| .github/workflows/ci-cd.yml | Adds a docs job and gates ketryx check on docs completion. |
| .github/workflows/_test.yml | Bumps actions/upload-artifact pin. |
| .github/workflows/_scheduled-test-hourly.yml | Bumps actions/upload-artifact pin. |
| .github/workflows/_scheduled-test-daily.yml | Bumps actions/upload-artifact pin. |
| .github/workflows/_scheduled-audit.yml | Bumps actions/upload-artifact pin. |
| .github/workflows/_package-publish.yml | Bumps actions/upload-artifact pin. |
| .github/workflows/_docs.yml | Adds reusable docs workflow that runs make docs. |
| .github/workflows/_docker-publish.yml | Bumps docker/build-push-action pin. |
| .github/workflows/_claude-code.yml | Bumps anthropics/claude-code-action pin. |
| .github/workflows/_build-native-only.yml | Bumps actions/upload-artifact pin. |
| .github/workflows/_audit.yml | Bumps actions/upload-artifact pin. |
| .github/labels.yml | Adds new Claude review verdict labels and expands SOP/type/security/scope label sets. |
| .github/copilot-instructions.md | Formatting updates in embedded examples. |
| .github/CLAUDE.md | Documents new docs workflow in CI overview and formatting tweaks. |
| .claude/settings.json | Adds Claude Code permissions/config for MCP and plugins. |
| .claude/scheduled_tasks.lock | Adds Claude scheduled task lock artifact. |
| .claude/launch.json | Adds local launch configs for running Launchpad against different environments. |
| @@ -0,0 +1 @@ | |||
| {"sessionId":"141e42d1-2313-4aa6-8525-e62a6191f6d1","pid":79441,"procStart":"Sun Apr 26 08:58:29 2026","acquiredAt":1777200880667} | |||
| "mcpServers": { | ||
| "playwright": { | ||
| "command": "npx", | ||
| "args": ["@playwright/mcp@latest"] |
| "charliermarsh.ruff", | ||
| "codecov.codecov", | ||
| "daelonsuzuka.nicegui", | ||
| "daelonsuzuka.nicegui", |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Why?
PR #620 (
release/v1.3.0→main) is blocked by a merge conflict inpyproject.toml. The conflict arose becausemainbumpeduvto>=0.11.6(fixing GHSA-pjjw-68hj-v9mw) after the release branch was cut.How?
Merges
mainintorelease/v1.3.0and resolves the single conflict by keepingmain's stricteruv>=0.11.6bound and its extended CVE comment, which supersedes the release branch'suv>=0.9.7.