From 412695c0d24f58e260d87475d8bb6fab90a961a4 Mon Sep 17 00:00:00 2001 From: Petr Date: Mon, 18 May 2026 08:25:51 +0200 Subject: [PATCH] =?UTF-8?q?chore(release):=200.43.0=20=E2=80=94=20Semantic?= =?UTF-8?q?=20Layer=20UI=20+=20Metastore=20error/type=20hardening?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pyproject.toml 0.42.0 → 0.43.0 (auto-synced into plugin.json + marketplace.json via `make version-sync`) - Changelog entry summarises the three lines of work merged via #308: * Full Semantic Layer page in `kbagent serve --ui` — 1:1 mirror of `kbagent semantic-layer` CLI surface (model CRUD, 5-entity CRUD, validate/export/diff/promote/import/build/encrypt-token, all routed through `/api/semantic-layer/*`, zero direct Metastore calls from JS). Relationships ERD ships as `flowchart TB` with hub-and-spoke layout, edge-label minimisation, and Math.min auto-fit so 80-edge overviews shrink to fit (~40%) and 15-edge hub drill-downs land at ~63%. * `_raise_api_error` walks exception/message/description/detail/ errors/json.dumps; rejects int `error` field. Surfaces real Metastore 422 text instead of "API error 422: 422". * Heuristic `build` now maps warehouse-native column types to the metastore's closed lowercase vocabulary before push. Closes the long-standing 422 on legacy untyped Storage tables. make lint / format-check / changelog-check / test: all green (3373 passed, 7 skipped). --- .claude-plugin/marketplace.json | 2 +- plugins/kbagent/.claude-plugin/plugin.json | 2 +- pyproject.toml | 2 +- src/keboola_agent_cli/changelog.py | 5 +++++ uv.lock | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 78336c02..4230621c 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "plugins": [ { "name": "kbagent", - "version": "0.42.0", + "version": "0.43.0", "source": "./plugins/kbagent", "description": "AI-friendly interface to Keboola Connection projects — explore configs, jobs, lineage, call MCP tools, manage dev branches, and debug SQL in workspaces", "category": "development" diff --git a/plugins/kbagent/.claude-plugin/plugin.json b/plugins/kbagent/.claude-plugin/plugin.json index 37d5cb1d..2ad9340d 100644 --- a/plugins/kbagent/.claude-plugin/plugin.json +++ b/plugins/kbagent/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "kbagent", - "version": "0.42.0", + "version": "0.43.0", "description": "AI-friendly interface to Keboola Connection projects — explore configs, jobs, lineage, call MCP tools, manage dev branches, and debug SQL in workspaces", "author": { "name": "Keboola", diff --git a/pyproject.toml b/pyproject.toml index 4d4a03ed..c719f7a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "keboola-agent-cli" -version = "0.42.0" +version = "0.43.0" description = "AI-friendly CLI for managing Keboola projects" readme = "README.md" requires-python = ">=3.12" diff --git a/src/keboola_agent_cli/changelog.py b/src/keboola_agent_cli/changelog.py index 7ccc8b1b..cf1e26ac 100644 --- a/src/keboola_agent_cli/changelog.py +++ b/src/keboola_agent_cli/changelog.py @@ -8,6 +8,11 @@ # Ordered newest-first. Each value is a list of brief one-line descriptions. CHANGELOG: dict[str, list[str]] = { + "0.43.0": [ + "New: full Semantic Layer management surface in `kbagent serve --ui` (closes #308). The web UI now mirrors every `kbagent semantic-layer` CLI operation 1:1 -- model CRUD (`/api/semantic-layer/models`), entity CRUD for all five kinds (metric / dataset / relationship / constraint / glossary), and the Phase-3 operations (validate, export, diff, promote, import, build, encrypt-token). The UI calls **zero** Metastore endpoints directly; every interaction goes through `/api/semantic-layer/*` on the same `kbagent serve` process so CLI parity is structural, not aspirational. Highlights: schema-driven add/edit drawers (one Pydantic schema per entity kind drives both Typer flags and the React form, no UI-side validation duplication); relationships view ships with a `flowchart TB` ERD (Mermaid) + a dataset-filter chip for hub-and-spoke drill-down + a parallel 'click to edit' edge list for hit-target reliability; constraints view groups rows by `constraintType` with collapsible `
` blocks and a 3-icon severity rail (critical / warning / info); datasets detail panel surfaces `fields[]` with role chips (`key=keboola/measure=green/dimension=zinc`). Builder/Importer/Promoter/Diff/Encrypt-Token are dedicated dialogs (`SemanticLayerDialogs.tsx`) with dry-run preview where the CLI offers it. The relationships ERD ships as `flowchart TB` (not `erDiagram`): erDiagram has no rankdir and laid every hub-and-spoke model out as a wide thin strip wasting ~70% of the canvas. flowchart TB puts the hub above its dependents, edge labels are trimmed to just the join type (`left` / `inner`; full relationship names live in the edge list below), auto-fit chooses `Math.min(fitX, fitY)` (cap 2.5, floor 0.4) so 80-edge overviews shrink to ~40% and 15-edge hub drill-downs land at ~63%, both fully readable.", + 'Fix: `BaseHttpClient._raise_api_error` now correctly surfaces Metastore validation messages instead of printing a bare HTTP status code. The Keboola Metastore answers 422 with `{"error": 422, "description": "..."}` (int in `error`, real text in `description`); the old parser used `body.get("error")` as the priority key, which evaluated to `422` and shadowed the real message -- the CLI rendered `API error 422: 422` and the operator had no actionable text. The new walker accepts `error` ONLY when it is a non-empty string, then falls through to `exception → message → description → detail → errors → json.dumps(body)` in priority order; FastAPI\'s `{"detail": [{loc, msg}]}` and Metastore\'s `{"errors": [{loc, msg}]}` list shapes are json-serialised so the message contains every diagnostic line, not the Python list repr. Four regression tests pin the new paths (int `error`, plain `description`, both list shapes) so the bare-status-code UX cannot return silently.', + 'Fix: `kbagent semantic-layer build` no longer HTTP-422s on legacy untyped Storage tables. The heuristic builder synthesises `fields[]` from a Storage `column_details[]` response; on legacy untyped tables the `basetype` is empty (`""`) and on typed tables it is warehouse-native (Snowflake `VARCHAR(255)`, `NUMBER(38,2)`, `TIMESTAMP_NTZ`, BigQuery `STRING`, ...). The Metastore only accepts a closed lowercase set (`string` / `integer` / `decimal` / `boolean` / `date` / `datetime` / `json`) for `fields[*].type`, so the heuristic builder used to push `""` or `"VARCHAR"` verbatim and 422 on every legacy table. New `_normalize_field_type(basetype)` strips parameter brackets and case-folds before mapping through `_FIELD_TYPE_MAP` (~30 warehouse aliases); empty/None falls through to `"string"` (safest default for an untyped column). A parametrized `TestNormalizeFieldType` covers every output bucket, parameterised types, case variants, and the unknown-UDT fall-through; the existing `test_heuristic_fallback` now asserts the field type was normalized to `"decimal"` end-to-end so the heuristic builder cannot regress to the pre-fix shape. Plugin docs (`plugins/kbagent/skills/kbagent/references/gotchas.md`) gain a `(since v0.41.10)` note on the normalization so any AI agent on an older kbagent has a documented escape path.', + ], "0.42.0": [ "Fix: workspace discoverability gap for data-app local dev (closes #304). David Ešner spent ~30 min and 4 wrong workspace IDs (including the `parameters.id` red herring) bringing up a Streamlit data app that reads via the Query Service -- because four different signals were missing or actively misleading. This release closes all four. (1) `kbagent workspace list` and `workspace detail` now accept `--branch` and follow the same `Info: Using production branch for read (active dev branch X ignored; pass --branch X to override)` banner as `storage buckets` / `config list`. Previously the commands silently scoped to the alias's pinned branch (carried over across sessions), returning a different workspace set than against the same alias one shell ago -- the original incident's root cause. `--branch` requires exactly one `--project` (branch IDs are per-project), mirroring the storage commands. (2) Each entry in `workspace list` / `workspace detail` JSON now carries `login_type`, `read_only`, `qs_compatible`, `database` and `warehouse`. The Storage API has always returned `connection.loginType` (snowflake-service-keypair / snowflake-person-sso / snowflake-legacy-service / default) and `readOnlyStorageAccess`; kbagent simply threw them away. Now they surface as new `Login Type` / `RO` / `QS` columns in the human-mode Rich table plus a `Login type` / `Read-only` / `Query Service compatible` block in `workspace detail`. `qs_compatible` is derived from the new conservative `QUERY_SERVICE_COMPATIBLE_LOGIN_TYPES` whitelist in `constants.py` (currently `snowflake-service-keypair` + `snowflake-person-sso`; `snowflake-legacy-service` stays OFF because the original issue confirmed it is rejected on the GCP us-east4 stack with `code: storage.executeQuery.notSupportedLoginType` even though it works on `connection.keboola.com`). False-negative-over-false-positive semantics: a `?` cell tells the caller 'not on the confirmed list, may still work' rather than blocking them. (3) New `workspace list --qs-compatible` filter pre-selects RO + whitelisted-loginType workspaces -- the canonical shape for a Streamlit / Quix data-app reading via the Query Service. (4) `config detail --component-id keboola.sandboxes --config-id ` now appends a `sandbox_annotation` block with `sandbox_service_id` (the misleading `parameters.id`) and `storage_workspace_id` (the actual Storage workspace ID resolved via `WorkspaceService.resolve_sandbox_workspace_id`). The annotation is JSON-structured and Rich-rendered; it appears ONLY in single-config mode to avoid N+1 in bulk fan-out. Empirically verified on padak-2-0 (project 10539) by pinning to dev branch 1297900: pre-fix `workspace list` returned 1 row from the dev branch with no banner, post-fix returns 22 rows from production WITH the banner explaining how to opt back in via `--branch 1297900`. New module-level `_classify_qs_compatibility` helper + `WorkspaceService.resolve_sandbox_workspace_id`. Tests: 3 in `test_workspace_cli.py::TestWorkspaceListIssue304` (branch flag propagation, multi-project rejection, qs filter propagation, active-branch banner), 1 in `test_workspace_cli.py::TestWorkspaceDetailIssue304`, 3 in `test_workspace_service.py::TestIssue304WorkspaceListEnrichment`, 3 in `test_workspace_service.py::TestIssue304ResolveSandboxWorkspaceId`, 1 in `test_workspace_service.py::TestIssue304GetWorkspaceEnrichment`, 3 in `test_cli.py::TestConfigDetail` for sandbox annotation (matching + orphan + non-sandbox-component negative).", ], diff --git a/uv.lock b/uv.lock index 5564ae81..582ba8ce 100644 --- a/uv.lock +++ b/uv.lock @@ -496,7 +496,7 @@ wheels = [ [[package]] name = "keboola-agent-cli" -version = "0.42.0" +version = "0.43.0" source = { editable = "." } dependencies = [ { name = "httpx" },