diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9931cfc01..ce45e8635 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,5 +1,5 @@ # aignostics code owners -* @aignostics/be-tada @helmut-hoffer-von-ankershoffen +* @aignostics/be-tada @aignostics/ml-eng-team-leads # Reference: diff --git a/CHANGELOG.md b/CHANGELOG.md index bcfbc3c9d..764fad252 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,98 @@ [๐Ÿ”ฌ Aignostics Python SDK](https://aignostics.readthedocs.io/en/latest/) +# [v1.4.0](https://github.com/aignostics/python-sdk/compare/v1.3.0..v1.4.0) - 2026-05-13 + +### โ›ฐ๏ธ Features + +- *(application)* Add CLI version document {list,describe,download} commands [PYSDK-122] - ([ed8d80c](https://github.com/aignostics/python-sdk/commit/ed8d80cbba79cad7d0665a7f678bffd77657420e)) +- *(launchpad)* Conditional Betterstack status page per environment [PYSDK-107] ([#599](https://github.com/aignostics/python-sdk/pull/599)) - ([1b838cc](https://github.com/aignostics/python-sdk/commit/1b838ccfc44ff85b1b0743e0bf5d93858efced9c)) +- *(platform)* Support external token providers and simplify caching - ([9007e37](https://github.com/aignostics/python-sdk/commit/9007e372426b9ca67e50278cfb302c6c9aa849c6)) +- *(platform)* Expose state/reason/metadata filters in Run.results() - ([fd34a7a](https://github.com/aignostics/python-sdk/commit/fd34a7a01ed3afd725022c5c2c82a52208c07628)) +- *(platform)* Add Documents.read_content() for /content endpoint [PYSDK-122] - ([a17d3f5](https://github.com/aignostics/python-sdk/commit/a17d3f55aa1bddcc1c301cce2c24db10df08a458)) +- *(platform)* Add Documents resource for application version release documents [PYSDK-122] - ([2c79b43](https://github.com/aignostics/python-sdk/commit/2c79b43c3cbfe82490e72b0a7676becab521608a)) +- *(platform)* Replace deprecated download_url with /file endpoint [PYSDK-109] ([#598](https://github.com/aignostics/python-sdk/pull/598)) - ([500012b](https://github.com/aignostics/python-sdk/commit/500012b0d26d05e8dee66eea42fefb0b6d181181)) + +### ๐Ÿ› Bug Fixes + +- *(application)* Silence pyright unused-variable warnings in document download - ([f3785e8](https://github.com/aignostics/python-sdk/commit/f3785e8b9720ad8f6ba969628ad503711ee9d8a4)) +- *(ci)* Add --frozen to uv run commands in CI workflows - ([88612d4](https://github.com/aignostics/python-sdk/commit/88612d4c4447c235651d02f37919ed2ff3b5c37e)) +- *(ci)* Fix slack release announcement json payload - ([5b081f7](https://github.com/aignostics/python-sdk/commit/5b081f7cad59c522976e7c9f973f63ff46bb1771)) +- *(ci)* Bump Dockerfile uv pin to 0.11.7 to match pyproject.toml >=0.11.6 PYSDK-98 ([#583](https://github.com/aignostics/python-sdk/pull/583)) - ([9f63222](https://github.com/aignostics/python-sdk/commit/9f63222779014027f22c721aa7c21a32792c1ab3)) +- *(deps)* Bump uv lower bound to >=0.11.6 for GHSA-pjjw-68hj-v9mw - ([4af3e90](https://github.com/aignostics/python-sdk/commit/4af3e90a2becddd8e6ab23041e88c17d0dafd3ed)) +- *(deps)* Bump pip lower bound to >=26.1 for CVE-2026-3219 [PYSDK-115] ([#606](https://github.com/aignostics/python-sdk/pull/606)) - ([487193f](https://github.com/aignostics/python-sdk/commit/487193f6c68a541f212b498000d2a54460e1af75)) +- *(deps)* Update dependency pyarrow to v23 ([#488](https://github.com/aignostics/python-sdk/pull/488)) - ([f75eb61](https://github.com/aignostics/python-sdk/commit/f75eb6126e58e0e13902197d91b5ec6228d15ede)) +- *(deps)* Update dependency fastparquet to v2026 ([#500](https://github.com/aignostics/python-sdk/pull/500)) - ([8f5d85a](https://github.com/aignostics/python-sdk/commit/8f5d85a43f41c29e22aa08364bbd4f714ef6e500)) +- *(deps)* Update dependency idc-index-data to v23.10.1 ([#506](https://github.com/aignostics/python-sdk/pull/506)) - ([9ce367c](https://github.com/aignostics/python-sdk/commit/9ce367c7ef0ca93dffb0d10206367ef29ccf06ae)) +- *(docker)* Add --no-build to uv sync --no-install-project stages - ([11cbe47](https://github.com/aignostics/python-sdk/commit/11cbe4715e3e35c5a154bbecc1d3a8ee3d2ee12b)) +- *(docs)* Correct skip:test:long_running label name in CLAUDE.md ([#577](https://github.com/aignostics/python-sdk/pull/577)) - ([709e166](https://github.com/aignostics/python-sdk/commit/709e166979147ff24f852472728874f223abdebb)) +- *(test)* Enable E2E pytest mark - ([d8eb626](https://github.com/aignostics/python-sdk/commit/d8eb626e3bca811f70e144bdfb62f5643d653a1f)) +- *(test)* Pin application version in E2E test - ([88f6ce4](https://github.com/aignostics/python-sdk/commit/88f6ce40ff92a2ec50a0e6a2b8f18f39698a9215)) +- Test after model update - ([18817d3](https://github.com/aignostics/python-sdk/commit/18817d3bc7468dc8e1d0c575715fa32e71028f4f)) +- Add page_size validation to paginate() to prevent infinite loop [PYSDK-108] ([#597](https://github.com/aignostics/python-sdk/pull/597)) - ([8dd4930](https://github.com/aignostics/python-sdk/commit/8dd4930c074dcde8b6a87e4384c9ff86fc0178ae)) +- Use correct board URL for classic Jira project in compass.yml PYSDK-86 ([#574](https://github.com/aignostics/python-sdk/pull/574)) - ([e6e846d](https://github.com/aignostics/python-sdk/commit/e6e846da418cb123f9118f50502b0aabe331bf69)) +- Use UUID for Process Level single_select in compass.yml PYSDK-85 ([#573](https://github.com/aignostics/python-sdk/pull/573)) - ([836956a](https://github.com/aignostics/python-sdk/commit/836956ac028270b571fba1326f6043b2012239b5)) +- Correct invalid compass.yml link types and custom field type PYSDK-83 ([#571](https://github.com/aignostics/python-sdk/pull/571)) - ([188e632](https://github.com/aignostics/python-sdk/commit/188e63292d07bbb16c8035ffad8664b6300feeea)) + +### ๐Ÿšœ Refactor + +- *(platform)* Address SonarCloud findings on documents PR [PYSDK-122] - ([83c8962](https://github.com/aignostics/python-sdk/commit/83c896243d46308ef7d364c4a03e447268083468)) +- *(platform)* Simplify document download and address PR review [PYSDK-122] - ([6194fc5](https://github.com/aignostics/python-sdk/commit/6194fc57d414e15e61380fb05b9cb8fd8d8570a4)) + +### ๐Ÿ“š Documentation + +- *(claude)* Add SDLC Configuration block to CLAUDE.md - ([2d2aad3](https://github.com/aignostics/python-sdk/commit/2d2aad33adbf219f0b1f25a1364da66b49f25566)) +- *(specs)* Fix Ketryx itemId cross-references โ€” use hyphens not underscores [PYSDK-121] - ([9492c95](https://github.com/aignostics/python-sdk/commit/9492c95b3f7669f4da970baed0bad29722e5d302)) +- Remove redundant SWR/SPEC for Betterstack feature [PYSDK-127] - ([32c592c](https://github.com/aignostics/python-sdk/commit/32c592c5f0026eb3bc05a641026d27017c1457b9)) +- Revert supply-chain vulnerability documentation from #580 - ([59506a2](https://github.com/aignostics/python-sdk/commit/59506a28e571bdaf14f631fb0bac9c24111a8ff7)) + +### ๐Ÿงช Testing + +- *(application)* Cover document CLI error paths and JSON output [PYSDK-122] - ([ad40a7b](https://github.com/aignostics/python-sdk/commit/ad40a7b838a6349ecf680c1afb08d6f2f1c0f0c8)) + +### โš™๏ธ Miscellaneous Tasks + +- *(ci)* Disable split_commits in git-cliff config - ([ef28b07](https://github.com/aignostics/python-sdk/commit/ef28b0729f17ecbb59be4624d17f178db9d1749f)) +- *(ci)* Exclude .claude/worktrees from pyright + gitignore [PYSDK-103] ([#591](https://github.com/aignostics/python-sdk/pull/591)) - ([206adde](https://github.com/aignostics/python-sdk/commit/206added394eadcd6f3b77861ac72b53f866a216)) +- *(codegen)* Regenerate aignx.codegen client from openapi v1.5.0 [PYSDK-122] - ([90ec7a4](https://github.com/aignostics/python-sdk/commit/90ec7a4678dcbe0bb0def4ba55b4e2a2b4110484)) +- *(deps)* Lock file maintenance - ([970a676](https://github.com/aignostics/python-sdk/commit/970a67620cc8e318679a11da65af27a91394ee8b)) +- *(deps)* Lock file maintenance ([#604](https://github.com/aignostics/python-sdk/pull/604)) - ([e1a8dbd](https://github.com/aignostics/python-sdk/commit/e1a8dbd7369ad6cfe7cd4a25193075c443ee3cb2)) +- *(deps)* Lock file maintenance ([#489](https://github.com/aignostics/python-sdk/pull/489)) - ([0c31a8b](https://github.com/aignostics/python-sdk/commit/0c31a8b5cf41886b9201de4fa08ab17433a7264b)) +- *(deps)* Upgrade myst-parser to v5, sphinx <10, swagger-plugin-for-sphinx to v7 ([#596](https://github.com/aignostics/python-sdk/pull/596)) - ([d3bdf6f](https://github.com/aignostics/python-sdk/commit/d3bdf6fd9d2f1ca3aed3e163b4e21f8a4ecdfc13)) +- *(deps)* Update actions/create-github-app-token action to v3.1.1 ([#595](https://github.com/aignostics/python-sdk/pull/595)) - ([d1bdc18](https://github.com/aignostics/python-sdk/commit/d1bdc18b67ec6a223da053b44dff07c8d324952f)) +- *(deps)* Update pandas requirement from <3,>=2.3.3 to >=2.3.3,<4 ([#396](https://github.com/aignostics/python-sdk/pull/396)) - ([292ca4c](https://github.com/aignostics/python-sdk/commit/292ca4cde16516fa19f97777092cdfa538844cd1)) +- *(deps)* Update github actions ([#560](https://github.com/aignostics/python-sdk/pull/560)) - ([a15d5b2](https://github.com/aignostics/python-sdk/commit/a15d5b22cd1ae6d9725a0f91ba947c2d54728897)) +- *(deps)* Remove pygments lower bound (intentional gap to test daily audit-vulnerabilities routine) [PYSDK-104] ([#592](https://github.com/aignostics/python-sdk/pull/592)) - ([daf15a0](https://github.com/aignostics/python-sdk/commit/daf15a0f7517706628430c1c082744e3523ac6ea)) +- *(deps)* Sweep Renovate/Dependabot bumps into pyproject.toml lower bounds PYSDK-93 ([#580](https://github.com/aignostics/python-sdk/pull/580)) - ([765cd70](https://github.com/aignostics/python-sdk/commit/765cd706f5eaa0ebc9a2a50d56f2ba8c6ece1c8f)) +- *(dev)* Update scalene profile target for v2 API [PYSDK-91] ([#579](https://github.com/aignostics/python-sdk/pull/579)) - ([10cd232](https://github.com/aignostics/python-sdk/commit/10cd232495e45e22c6a7664e9bf02da7ed5dabc4)) +- *(docs)* Sync supply-chain record with pyproject after #531/#553 [PYSDK-114] ([#605](https://github.com/aignostics/python-sdk/pull/605)) - ([3ee8ac2](https://github.com/aignostics/python-sdk/commit/3ee8ac2596b52805b77ae4ae293306a409a9da15)) +- *(renovate)* Run long_running tests on bumps of runtime-critical packages PYSDK-111 ([#601](https://github.com/aignostics/python-sdk/pull/601)) - ([866ac7b](https://github.com/aignostics/python-sdk/commit/866ac7b1423ad127ed068bef56d4ed1abcd7cca9)) +- Enable build in uv sync in Dockerfile - ([48eda3d](https://github.com/aignostics/python-sdk/commit/48eda3d9533a10b2ca223a0bcc00ca162dc268d1)) +- Address comments - ([668526c](https://github.com/aignostics/python-sdk/commit/668526cdde24eeca24b6b219dda8b77ef439e688)) +- Address review comments - ([c9d8a15](https://github.com/aignostics/python-sdk/commit/c9d8a15012520b27b1171b3d411fbc6cf321a908)) +- Bump SPEC dates and fix lint/test fallout from documents codegen [PYSDK-122] - ([c72c3dd](https://github.com/aignostics/python-sdk/commit/c72c3dda8311c1d951304c1bcef8efd47c9769ee)) +- TODO - add commit content - ([e8394b2](https://github.com/aignostics/python-sdk/commit/e8394b221ba98e0301ba34e64071dce43e82a883)) +- Revert uv.lock to release/v1.3.0 to avoid merge conflict with main - ([d66c597](https://github.com/aignostics/python-sdk/commit/d66c597f94538b24287aaef0c4dd7d6194224c10)) +- Add Playwright MCP server config to .mcp.json [PYSDK-117] ([#608](https://github.com/aignostics/python-sdk/pull/608)) - ([c15534f](https://github.com/aignostics/python-sdk/commit/c15534feeae706ad268eb2946346b3140a6d9f27)) +- Fix PR review re-triggered by claude:review verdict labels PYSDK-116 ([#607](https://github.com/aignostics/python-sdk/pull/607)) - ([93f83aa](https://github.com/aignostics/python-sdk/commit/93f83aa64a56a98129e81bb0a3453e823fce501c)) +- Add #python-sdk-announcements to compass.yml PYSDK-113 ([#602](https://github.com/aignostics/python-sdk/pull/602)) - ([c472080](https://github.com/aignostics/python-sdk/commit/c472080879d6f8c3de2d38af459ffda3b0d8859d)) +- Add docs build as parallel CI job [PYSDK-110] ([#600](https://github.com/aignostics/python-sdk/pull/600)) - ([3c9fe08](https://github.com/aignostics/python-sdk/commit/3c9fe088a5ceaca71f71e43d9c7cc6cad7f01f39)) +- Enable aignostics/claude-plugins qms plugin project-wide [PYSDK-96] ([#582](https://github.com/aignostics/python-sdk/pull/582)) - ([32025d3](https://github.com/aignostics/python-sdk/commit/32025d370d408a668d17e60b9720d7ed53c99591)) +- Remove broken codecov VS Code extension from recommendations ([#578](https://github.com/aignostics/python-sdk/pull/578)) - ([e02bab9](https://github.com/aignostics/python-sdk/commit/e02bab9432352f41a566a158155db3e071a4277d)) +- Fix link types and add missing Ketryx link in compass.yml [PYSDK-88] ([#576](https://github.com/aignostics/python-sdk/pull/576)) - ([2db76b2](https://github.com/aignostics/python-sdk/commit/2db76b2ccaf1be811ba1867c3efcfa3a27d3d4d0)) +- Add #python-sdk-notifications Slack channel to compass.yml PYSDK-87 ([#575](https://github.com/aignostics/python-sdk/pull/575)) - ([47bbc4b](https://github.com/aignostics/python-sdk/commit/47bbc4b82188bb617bb3f5571342e72d57ce1edf)) +- Add service dependencies and process level to compass.yml [PYSDK-84] ([#572](https://github.com/aignostics/python-sdk/pull/572)) - ([af2a321](https://github.com/aignostics/python-sdk/commit/af2a3214abb69052c84142192abec5a7a419d5a5)) +- Trigger Compass compass.yml re-sync - ([e941b27](https://github.com/aignostics/python-sdk/commit/e941b27ac368bda7137cdd8f0923c3dd94d359e4)) +- Update compass.yml โ€” Jira board, status page, tier, lifecycle and metadata [PYSDK-82] ([#570](https://github.com/aignostics/python-sdk/pull/570)) - ([1fd4536](https://github.com/aignostics/python-sdk/commit/1fd4536db2299dd8f21dcec95fea18d5c3a040bf)) + +### ๐Ÿ›ก๏ธ Security + +- *(ci)* Add sop:*, type:*, security:*, scope:*, auto-merge labels [PYSDK-94] ([#581](https://github.com/aignostics/python-sdk/pull/581)) - ([234e358](https://github.com/aignostics/python-sdk/commit/234e3586e9e943aa79b5d7fbafcf82fe027a5104)) +- *(deps)* Update dependency urllib3 to v2.7.0 [security] ([#637](https://github.com/aignostics/python-sdk/pull/637)) - ([b4bcf89](https://github.com/aignostics/python-sdk/commit/b4bcf892d7562f7803b71cdca0159690b366a8d0)) +- *(deps)* Update dependency jupyterlab to v4.5.7 [security] ([#616](https://github.com/aignostics/python-sdk/pull/616)) - ([f5ab258](https://github.com/aignostics/python-sdk/commit/f5ab258b74a045266626529dd218f3488106f420)) +- *(deps)* Bump nicegui to v3.11.0 [security] + adapt to 3.10/3.11 GUI lifecycle changes ([#531](https://github.com/aignostics/python-sdk/pull/531)) - ([6682f84](https://github.com/aignostics/python-sdk/commit/6682f847afc5a1c9f299fab459b7fb7caa3652d7)) +- *(deps)* Bump pygments lower bound to >=2.20.0 for CVE-2026-4539 [PYSDK-106] ([#594](https://github.com/aignostics/python-sdk/pull/594)) - ([43e91b2](https://github.com/aignostics/python-sdk/commit/43e91b296426580dcf3b72849aff5a53b561f1cc)) + + # [v1.3.0](https://github.com/aignostics/python-sdk/compare/v1.2.0..v1.3.0) - 2026-05-04 ### โ›ฐ๏ธ Features 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/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/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/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('