Skip to content

chore: merge main into release/v1.3.0 to resolve conflicts#621

Closed
olivermeyer wants to merge 39 commits into
release/v1.3.0from
chore/merge-main-into-release-v1.3.0
Closed

chore: merge main into release/v1.3.0 to resolve conflicts#621
olivermeyer wants to merge 39 commits into
release/v1.3.0from
chore/merge-main-into-release-v1.3.0

Conversation

@olivermeyer
Copy link
Copy Markdown
Collaborator

Why?
PR #620 (release/v1.3.0main) is blocked by a merge conflict in pyproject.toml. The conflict arose because main bumped uv to >=0.11.6 (fixing GHSA-pjjw-68hj-v9mw) after the release branch was cut.

How?
Merges main into release/v1.3.0 and resolves the single conflict by keeping main's stricter uv>=0.11.6 bound and its extended CVE comment, which supersedes the release branch's uv>=0.9.7.

…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>
renovate Bot and others added 9 commits April 26, 2026 21:15
* 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>
Copilot AI review requested due to automatic review settings May 4, 2026 09:35
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 4, 2026

@olivermeyer
Copy link
Copy Markdown
Collaborator Author

Closing in favour of a cleaner targeted fix directly on the release branch.

@olivermeyer olivermeyer closed this May 4, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 /file endpoint support for resolving short-lived artifact download URLs (new Artifact handle + 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}
Comment thread .mcp.json
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
Comment thread .vscode/extensions.json
"charliermarsh.ruff",
"codecov.codecov",
"daelonsuzuka.nicegui",
"daelonsuzuka.nicegui",
@codecov
Copy link
Copy Markdown

codecov Bot commented May 4, 2026

Codecov Report

❌ Patch coverage is 91.50000% with 17 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...application/_gui/_page_application_run_describe.py 90.16% 4 Missing and 2 partials ⚠️
src/aignostics/bucket/_gui.py 0.00% 6 Missing ⚠️
...ics/application/_gui/_page_application_describe.py 50.00% 3 Missing and 1 partial ⚠️
src/aignostics/platform/resources/runs.py 98.59% 1 Missing ⚠️
Files with missing lines Coverage Δ
src/aignostics/application/_download.py 69.71% <100.00%> (-8.70%) ⬇️
src/aignostics/application/_gui/_frame.py 76.47% <ø> (ø)
src/aignostics/application/_gui/_page_index.py 76.74% <ø> (ø)
src/aignostics/application/_utils.py 88.88% <ø> (-4.05%) ⬇️
src/aignostics/bucket/_service.py 77.77% <100.00%> (ø)
src/aignostics/dataset/_gui.py 81.20% <100.00%> (ø)
src/aignostics/gui/_frame.py 78.65% <100.00%> (+0.97%) ⬆️
src/aignostics/platform/__init__.py 100.00% <100.00%> (ø)
src/aignostics/platform/_client.py 93.68% <ø> (ø)
src/aignostics/platform/_constants.py 100.00% <100.00%> (ø)
... and 11 more

... and 4 files with indirect coverage changes

@olivermeyer olivermeyer deleted the chore/merge-main-into-release-v1.3.0 branch May 7, 2026 08:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants