From ef431e8c8160bf4fb2a72dbcfc69306b6a2a28b5 Mon Sep 17 00:00:00 2001 From: "aignostics-release-bot[bot]" Date: Thu, 7 May 2026 08:24:54 +0000 Subject: [PATCH 01/16] =?UTF-8?q?Bump=20version:=201.3.0=20=E2=86=92=201.4?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLI_REFERENCE.md | 2 +- VERSION | 2 +- aignostics.spec | 2 +- docs/source/conf.py | 2 +- examples/notebook.py | 2 +- pyproject.toml | 4 ++-- sonar-project.properties | 2 +- uv.lock | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CLI_REFERENCE.md b/CLI_REFERENCE.md index c5fc0adab..f58886fe9 100644 --- a/CLI_REFERENCE.md +++ b/CLI_REFERENCE.md @@ -14,7 +14,7 @@ $ aignostics [OPTIONS] COMMAND [ARGS]... * `--show-completion`: Show completion for the current shell, to copy it or customize the installation. * `--help`: Show this message and exit. -๐Ÿ”ฌ Aignostics Python SDK v1.3.0 - built with love in Berlin ๐Ÿป // Python v3.14.3 +๐Ÿ”ฌ Aignostics Python SDK v1.4.0 - built with love in Berlin ๐Ÿป // Python v3.14.3 **Commands**: diff --git a/VERSION b/VERSION index f0bb29e76..88c5fb891 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.3.0 +1.4.0 diff --git a/aignostics.spec b/aignostics.spec index 542382ea8..87694ee05 100644 --- a/aignostics.spec +++ b/aignostics.spec @@ -124,7 +124,7 @@ if is_darwin: name='aignostics.app', icon='logo.ico', bundle_identifier='com.aignostics.launchpad', - version='1.3.0', + version='1.4.0', info_plist={ 'NSPrincipalClass': 'NSApplication', 'NSAppleScriptEnabled': False, diff --git a/docs/source/conf.py b/docs/source/conf.py index a96a5e0a7..768f7ce65 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -34,7 +34,7 @@ project = "aignostics" author = "Helmut Hoffer von Ankershoffen" copyright = f" (c) 2025-{datetime.now(UTC).year} Aignostics GmbH, Author: {author}" # noqa: A001 -version = "1.3.0" +version = "1.4.0" release = version github_username = "aignostics" github_repository = "python-sdk" diff --git a/examples/notebook.py b/examples/notebook.py index e4afc6486..c33a468f3 100644 --- a/examples/notebook.py +++ b/examples/notebook.py @@ -2,7 +2,7 @@ # requires-python = ">=3.13" # dependencies = [ # "marimo", -# "aignostics==1.3.0", +# "aignostics==1.4.0", # ] # /// diff --git a/pyproject.toml b/pyproject.toml index e456a9c70..65719af77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "aignostics" -version = "1.3.0" +version = "1.4.0" description = "๐Ÿ”ฌ Python SDK providing access to the Aignostics Platform. Includes Aignostics Launchpad (Desktop Application), Aignostics CLI (Command-Line Interface), example notebooks, and Aignostics Client Library." readme = "README.md" authors = [ @@ -411,7 +411,7 @@ source = ["src/"] [tool.bumpversion] -current_version = "1.3.0" +current_version = "1.4.0" parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)" serialize = ["{major}.{minor}.{patch}"] search = "{current_version}" diff --git a/sonar-project.properties b/sonar-project.properties index f92809925..a29132fdb 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,6 +1,6 @@ sonar.projectKey=aignostics_python-sdk sonar.organization=aignostics -sonar.projectVersion=1.3.0 +sonar.projectVersion=1.4.0 sonar.projectDescription=๐Ÿ”ฌ Python SDK providing access to Aignostics AI services. sonar.links.homepage=https://aignostics.readthedocs.io/en/latest/ sonar.links.scm=https://github.com/aignostics/python-sdk diff --git a/uv.lock b/uv.lock index f901c5b01..6dd771738 100644 --- a/uv.lock +++ b/uv.lock @@ -30,7 +30,7 @@ wheels = [ [[package]] name = "aignostics" -version = "1.3.0" +version = "1.4.0" source = { editable = "." } dependencies = [ { name = "aiohttp" }, From 88f6ce40ff92a2ec50a0e6a2b8f18f39698a9215 Mon Sep 17 00:00:00 2001 From: Oliver Meyer Date: Mon, 11 May 2026 12:24:47 +0200 Subject: [PATCH 02/16] fix(test): pin application version in E2E test --- tests/aignostics/application/cli_test.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tests/aignostics/application/cli_test.py b/tests/aignostics/application/cli_test.py index 4c5b9aeb0..02d27bdd8 100644 --- a/tests/aignostics/application/cli_test.py +++ b/tests/aignostics/application/cli_test.py @@ -205,21 +205,27 @@ def test_cli_application_describe_not_found(runner: CliRunner, record_property) assert "Application with ID 'unknown' not found." in normalize_output(result.output) -@pytest.mark.e2e +# @pytest.mark.e2e @pytest.mark.timeout(timeout=60) def test_cli_application_dump_schemata(runner: CliRunner, tmp_path: Path, record_property) -> None: """Check application dump schemata works as expected.""" record_property("tested-item-id", "SPEC-APPLICATION-SERVICE") result = runner.invoke( - cli, ["application", "dump-schemata", HETA_APPLICATION_ID, "--destination", str(tmp_path), "--zip"] + cli, + [ + "application", + "dump-schemata", + HETA_APPLICATION_ID, + "--application-version", + HETA_APPLICATION_VERSION, + "--destination", + str(tmp_path), + "--zip", + ], ) - application_version = ApplicationService().application_version(HETA_APPLICATION_ID) - application_version = ApplicationService().application_version(HETA_APPLICATION_ID) assert result.exit_code == 0 assert "Zipped 11 files" in normalize_output(result.output) - zip_file = sanitize_path( - Path(tmp_path / f"{HETA_APPLICATION_ID}_{application_version.version_number}_schemata.zip") - ) + zip_file = sanitize_path(Path(tmp_path / f"{HETA_APPLICATION_ID}_{HETA_APPLICATION_VERSION}_schemata.zip")) assert zip_file.exists(), f"Expected zip file {zip_file} not found" From 32c592c5f0026eb3bc05a641026d27017c1457b9 Mon Sep 17 00:00:00 2001 From: Oliver Meyer Date: Mon, 11 May 2026 16:29:21 +0200 Subject: [PATCH 03/16] docs: remove redundant SWR/SPEC for Betterstack feature [PYSDK-127] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The user-facing contract was already covered by existing artefacts: SWR-SYSTEM-GUI-HEALTH-1 authorises health visibility in the Launchpad footer; SPEC_GUI_SERVICE FR-06 covers the embedded status indicator; SPEC_PLATFORM_SERVICE FR-04 covers per-environment endpoint config, and ยง7.2 covers URL validation. The per-environment Betterstack URL resolution is a design refinement, not a new design input. Co-Authored-By: Claude Opus 4.7 (1M context) --- requirements/SWR_SYSTEM_GUI_STATUS_PAGE_1.md | 23 --- specifications/SPEC-LAUNCHPAD-STATUS-PAGE.md | 194 ------------------- 2 files changed, 217 deletions(-) delete mode 100644 requirements/SWR_SYSTEM_GUI_STATUS_PAGE_1.md delete mode 100644 specifications/SPEC-LAUNCHPAD-STATUS-PAGE.md diff --git a/requirements/SWR_SYSTEM_GUI_STATUS_PAGE_1.md b/requirements/SWR_SYSTEM_GUI_STATUS_PAGE_1.md deleted file mode 100644 index 8da62c7d8..000000000 --- a/requirements/SWR_SYSTEM_GUI_STATUS_PAGE_1.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -itemId: SWR-SYSTEM-GUI-STATUS-PAGE-1 -itemTitle: Per-Environment Betterstack Status Page in Launchpad -itemHasParent: SHR-SYSTEM-1 -itemType: Requirement -Requirement type: FUNCTIONAL -Module: System -Layer: GUI ---- - -As a Launchpad user, I expect the embedded Betterstack status badge and the "Check Platform Status" link to reflect only the Aignostics Platform environment my Launchpad is connected to (as configured by `AIGNOSTICS_API_ROOT`), so that I can assess the operational health of the services I actually depend on without being distracted by, or misled by, the health of unrelated environments. - -The Launchpad shall resolve the public Betterstack status page URL from the configured platform environment as follows: - -- when connected to the production environment (`AIGNOSTICS_API_ROOT` = `https://platform.aignostics.com`), the Launchpad shall embed the badge of, and link to, `https://status.platform.aignostics.com`; -- when connected to the staging environment (`AIGNOSTICS_API_ROOT` = `https://platform-staging.aignostics.com`), the Launchpad shall embed the badge of, and link to, `https://status.platform-staging.aignostics.com`; -- when connected to a dev or test environment, or to any other environment for which no per-environment public Betterstack status page is configured, the Launchpad shall not render the Betterstack badge in its footer and shall not render the "Check Platform Status" item in its right-side menu. - -The user shall be able to override the resolved status page URL through the `AIGNOSTICS_STATUS_PAGE_URL` environment variable or the equivalent constructor argument, including overriding it to an empty value to suppress the badge and link. - -The Launchpad shall validate any user-supplied status page URL at configuration time and reject values that are not well-formed http(s) URLs or that contain characters that could break out of an HTML attribute when rendered (`"`, `'`, `<`, `>`, backtick, backslash, whitespace), so that the Launchpad cannot be tricked into rendering attacker-controlled markup through this configuration. - -When the Launchpad does not render the badge or the menu link, no degraded-state placeholder is shown โ€” both surfaces are simply omitted from the layout. diff --git a/specifications/SPEC-LAUNCHPAD-STATUS-PAGE.md b/specifications/SPEC-LAUNCHPAD-STATUS-PAGE.md deleted file mode 100644 index 49d9b4462..000000000 --- a/specifications/SPEC-LAUNCHPAD-STATUS-PAGE.md +++ /dev/null @@ -1,194 +0,0 @@ ---- -itemId: SPEC-LAUNCHPAD-STATUS-PAGE -itemTitle: Per-Environment Betterstack Status Page in Launchpad -itemType: Software Item Spec -itemFulfills: SWR-SYSTEM-GUI-STATUS-PAGE-1 -itemIsRelatedTo: SPEC-GUI-SERVICE, SPEC-PLATFORM-SERVICE, SPEC-SYSTEM-SERVICE -Module: System -Layer: GUI / Platform Service -Version: 1.0.0 -Date: 2026-04-26 ---- - -## 1. Description - -### 1.1 Purpose - -This specification describes how the Aignostics Launchpad (Desktop Application, NiceGUI-based) renders the embedded Betterstack status badge in its footer and the "Check Platform Status" link in its right-side menu so that both reflect only the Aignostics Platform environment the Launchpad is currently connected to (i.e., the environment selected by `AIGNOSTICS_API_ROOT`). - -The motivation is that the legacy aggregate page at `https://status.aignostics.com` covers production *and* staging *and* unrelated services (Console, Portal, Career Site, Website). A user running the Launchpad against a single environment is best served by the corresponding **narrower** Betterstack property of that same environment, with no badge or link rendered when no per-environment Betterstack property exists (dev, test, or unknown environments). - -### 1.2 Functional Requirements - -The Launchpad shall: - -- **[FR-01]** Resolve the public Betterstack status page URL from the configured `api_root` of the platform `Settings` model. -- **[FR-02]** Use `https://status.platform.aignostics.com` for production (`https://platform.aignostics.com`) and `https://status.platform-staging.aignostics.com` for staging (`https://platform-staging.aignostics.com`). -- **[FR-03]** Use `None` (i.e., no public per-environment status page) for the dev environment (`https://platform-dev.aignostics.ai`) and the test environment (`https://platform-test.aignostics.ai`), and for any unknown `api_root` whose auth fields are otherwise fully provided. -- **[FR-04]** Allow the user to override the resolved value through the `AIGNOSTICS_STATUS_PAGE_URL` environment variable or the `status_page_url` constructor argument of `Settings`. An empty string is treated as `None`. -- **[FR-05]** Validate the resolved value at `Settings` construction time, rejecting values that are not well-formed http(s) URLs and values that contain `"`, `'`, `<`, `>`, backtick, backslash, or whitespace characters. -- **[FR-06]** When the resolved value is non-`None`, render the Betterstack badge in the footer (as a 250ร—30 iframe pointing at `/badge?theme=dark`) and a "Check Platform Status" link in the right-side menu pointing at ``. -- **[FR-07]** When the resolved value is `None`, omit the Betterstack badge from the footer and omit the "Check Platform Status" item from the right-side menu โ€” no degraded-state placeholder is rendered. -- **[FR-08]** Refresh the Betterstack iframe every 30 seconds (in alignment with the existing health-update interval), guarded so the refresh is a safe no-op when the iframe is absent from the DOM. - -### 1.3 Non-Functional Requirements - -- **Security**: User-controlled values must not be able to inject markup into the Launchpad webview. Defence-in-depth: (1) `Settings.status_page_url` is validated by `_validate_optional_url` before reaching the GUI layer; (2) the iframe is rendered via NiceGUI's `ui.element('iframe')` with attributes assigned through the props dict, so attribute values flow through Vue data binding rather than raw HTML construction. -- **Backwards compatibility**: An unknown `api_root` (with all auth fields provided) must produce a safe default (`None`, no badge, no link) rather than raising an error. The aggregate `https://status.aignostics.com` page must remain unchanged and reachable for users who navigate to it directly. -- **Resilience**: The 30-second iframe-refresh JS must remain safe when the iframe is absent from the DOM (dev/test or override-to-`None` cases). The behaviour shall not depend on the order in which the timer first fires relative to first DOM mount. - -### 1.4 Constraints and Limitations - -- The dev and test environments do not currently have a dedicated public Betterstack property; this specification deliberately treats that as a "no badge, no link" state, not an error. -- The `Settings` `pre_init` model validator returns early when all auth fields are explicitly provided. In that path, the per-environment match block is skipped, and `status_page_url` retains its declared default (`None`) unless the caller supplied it explicitly. - ---- - -## 2. Architecture and Design - -### 2.1 Files Touched - -| File | Role | -| --- | --- | -| `src/aignostics/platform/_constants.py` | Per-environment URL constants `STATUS_PAGE_URL_DEV`, `STATUS_PAGE_URL_TEST`, `STATUS_PAGE_URL_STAGING`, `STATUS_PAGE_URL_PRODUCTION`. | -| `src/aignostics/platform/_settings.py` | `Settings.status_page_url: str \| None` field with `BeforeValidator(_validate_optional_url)`; resolution inside the existing `pre_init` `match...case` block alongside the auth endpoints; helper `_validate_optional_url(value: str \| None) -> str \| None`. | -| `src/aignostics/platform/__init__.py` | Re-exports the four `STATUS_PAGE_URL_*` constants for downstream consumers. | -| `src/aignostics/gui/_frame.py` | Reads `settings().status_page_url` once after the context manager `yield`. Conditionally renders the right-menu "Check Platform Status" item, the footer iframe, and the 30-s refresh JS based on this value. Defensive JS element guard `if (iframe) { iframe.src = iframe.src; }` so the refresh never throws when the iframe is absent. | -| `tests/aignostics/platform/settings_test.py` | Per-environment assertions on `status_page_url` and parametrised rejection of invalid/unsafe URLs. | - -### 2.2 Resolution Algorithm - -```text -input: api_root (string), explicit overrides (env var, constructor argument) -output: status_page_url: str | None - -1. If the user provided `status_page_url` explicitly (constructor arg or - `AIGNOSTICS_STATUS_PAGE_URL` env var): - โ†’ run `_validate_optional_url`; on success use that value. -2. Else, in the existing `pre_init` `match...case`: - - api_root == API_ROOT_DEV โ†’ setdefault to STATUS_PAGE_URL_DEV (None) - - api_root == API_ROOT_TEST โ†’ setdefault to STATUS_PAGE_URL_TEST (None) - - api_root == API_ROOT_STAGING โ†’ setdefault to STATUS_PAGE_URL_STAGING - - api_root == API_ROOT_PRODUCTION โ†’ setdefault to STATUS_PAGE_URL_PRODUCTION - - any other api_root with all auth fields supplied: - โ†’ field default applies (None) - - any other api_root without auth fields: - โ†’ ValueError UNKNOWN_ENDPOINT_URL -``` - -### 2.3 Validation - -`_validate_optional_url(value: str | None) -> str | None` is registered as a Pydantic `BeforeValidator` on the field: - -1. `None` โ†’ `None`. -2. `""` โ†’ `None` (env-var loaders may produce an empty string when the variable is set but empty; treating it as `None` matches the dev/test default). -3. Non-empty string: - 1. Reject if it contains any of `"`, `'`, `<`, `>`, backtick, backslash, or whitespace (RFC 3986 requires those to be percent-encoded; raw forms are either malformed or an injection attempt). - 2. Otherwise, delegate to the existing `_validate_url` (scheme must be `http` or `https`; netloc must be non-empty). - -### 2.4 Rendering - -In `gui/_frame.py`: - -```python -status_page_url = settings().status_page_url # resolved once, reused - -if status_page_url: - # right-menu: "Check Platform Status" item with ui.link(...) - -if status_page_url: - # footer: NiceGUI iframe element, attributes via props dict (no raw HTML) - iframe = ui.element("iframe") - iframe.props["id"] = "betterstack" - iframe.props["src"] = urljoin(status_page_url + "/", "badge?theme=dark") - iframe.props["width"] = "250" - iframe.props["height"] = "30" - iframe.props["frameborder"] = "0" - iframe.props["scrolling"] = "no" - iframe.style("color-scheme: dark; margin-left: 0px;") - -# 30-s refresh, runs unconditionally; element existence is guarded in JS. -ui.run_javascript( - "var iframe = document.getElementById('betterstack');" - "if (iframe) { iframe.src = iframe.src; }" -) -``` - -The iframe is rendered as a NiceGUI `ui.element('iframe')` rather than `ui.html('