diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 543e0d1e..efc92183 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "plugins": [ { "name": "kbagent", - "version": "0.96.2", + "version": "0.97.0", "source": "./plugins/kbagent", "description": "DEPRECATED — install from keboola/ai-kit: /plugin marketplace add keboola/ai-kit && /plugin install kbagent@keboola-claude-kit — AI-friendly interface to Keboola Connection projects — explore configs, jobs, lineage, sync configs as files, manage dev branches, and debug SQL in workspaces", "category": "development" diff --git a/CLAUDE.md b/CLAUDE.md index 61425ae9..743534d5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1130,7 +1130,7 @@ kbagent semantic-layer reference-data set --project P [--model M] --dimension D kbagent semantic-layer reference-data delete --project P --id ID [--yes] # Alias: `kbagent sl ...` (hidden) is equivalent to `kbagent semantic-layer ...`. # semantic-layer READS work with any valid, non-disabled, non-expired Storage token -# (vNEXT, PSGO-282): the Metastore no longer requires a master token for GET/List. WRITES +# (0.97.0, PSGO-282): the Metastore no longer requires a master token for GET/List. WRITES # (add/edit/remove/import/promote/build/scope add|remove|set) still need a project-admin token # (master token or any admin-role user token) -- a non-admin token 403s on the write. Before # PSGO-282 the Metastore's auth gate rejected EVERY valid non-master token, including reads, @@ -1139,7 +1139,7 @@ kbagent semantic-layer reference-data delete --project P --id ID [--yes] # a safety net for a deployment that predates the fix. Other unexplained 401s anywhere map to # AUTH_REJECTED instead of the false "Invalid or expired token" (INVALID_TOKEN stays for 401s # that DO blame the credential). Version gate for this entry lives in gotchas.md -- `(since -# vNEXT)` cannot be written on these `# ` comment lines (check_version_gates.py parses them as +# 0.97.0)` cannot be written on these `# ` comment lines (check_version_gates.py parses them as # ATX headings). kbagent http get PATH [--timeout SECONDS] diff --git a/plugins/kbagent/.claude-plugin/plugin.json b/plugins/kbagent/.claude-plugin/plugin.json index 0164d931..46f82bf3 100644 --- a/plugins/kbagent/.claude-plugin/plugin.json +++ b/plugins/kbagent/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "kbagent", - "version": "0.96.2", + "version": "0.97.0", "description": "AI-friendly interface to Keboola Connection projects — explore configs, jobs, lineage, sync configs as files, manage dev branches, and debug SQL in workspaces", "author": { "name": "Keboola", diff --git a/plugins/kbagent/agents/keboola-expert.md b/plugins/kbagent/agents/keboola-expert.md index 24e510bf..4ff11cb3 100644 --- a/plugins/kbagent/agents/keboola-expert.md +++ b/plugins/kbagent/agents/keboola-expert.md @@ -151,7 +151,7 @@ been retired, so its absence is NOT a promise (see §1 Rule 6). | Call the running `kbagent serve` from a scheduled-agent subprocess | `kbagent http get/post/patch/delete ` -- uses the `KBAGENT_SERVE_URL` + `KBAGENT_SERVE_TOKEN` the scheduler injects; `http get /openapi.json` discovers endpoints | forking `kbagent ` (also fine -- `KBAGENT_CONFIG_DIR` is propagated) | `curl $KBAGENT_SERVE_URL/...` by hand (loses the auth header, error mapping and JSON formatting `kbagent http` adds) | | Launch the web UI for an end-user | `kbagent serve --ui [--port PORT]` -- one FastAPI process mounts the bundled React SPA and sets an HttpOnly `kbagent_session` cookie, so SSE works with no token in the URL or JS heap | the legacy Vite + Node BFF dev flow (`web/README.md`) | inventing a `--token-in-url` flag; running uvicorn directly against the dist (the cookie bootstrap only fires from `serve --ui`) | | Schedule / manage Agent Tasks | `kbagent agent ` -- CRUD, `run [--stream]`, history, `test`/`cron-preview`/`prompt-improve`. Local-only; cron firing needs `kbagent serve`. See [agent-tasks-cli-workflow](../skills/kbagent/references/agent-tasks-cli-workflow.md) | `kbagent http /agents...` inside scheduled subprocesses | hand-editing `agents.json` | -| Read a semantic-layer model (models, metrics, datasets, constraints) | `kbagent --json semantic-layer show --project P [--model M] [--type metric\|dataset\|relationship\|constraint\|glossary]`; `model list`; `search-context` / `get-context` for glob/id lookup; `validate [--deep]` before trusting one. Any valid, non-disabled, non-expired Storage token works for reads (vNEXT, PSGO-282 -- the metastore no longer requires a master token for GET/List); a stale `MISSING_MASTER_TOKEN` on a plain read means an outdated kbagent still carrying the old reclassification, not a real gate | -- | hand-rolled `httpx` loops against `metastore.*.keboola.com` (bypasses retry/backoff and the kbagent error envelope) | +| Read a semantic-layer model (models, metrics, datasets, constraints) | `kbagent --json semantic-layer show --project P [--model M] [--type metric\|dataset\|relationship\|constraint\|glossary]`; `model list`; `search-context` / `get-context` for glob/id lookup; `validate [--deep]` before trusting one. Any valid, non-disabled, non-expired Storage token works for reads (0.97.0, PSGO-282 -- the metastore no longer requires a master token for GET/List); a stale `MISSING_MASTER_TOKEN` on a plain read means an outdated kbagent still carrying the old reclassification, not a real gate | -- | hand-rolled `httpx` loops against `metastore.*.keboola.com` (bypasses retry/backoff and the kbagent error envelope) | | ANY semantic-layer write (add / edit / remove / import / promote / build / scope add|remove|set|request-create) | `kbagent semantic-layer export` FIRST (the metastore has no soft-delete and no version history -- the snapshot is the only restore path), then the write, `--dry-run` where offered. Needs a token whose Storage Admin role is `admin` on the project (a master token qualifies, but so does any project-admin user token as of PSGO-282) -- a non-admin token still gets `MISSING_MASTER_TOKEN`/403 on writes, just not on reads. `Read` [semantic-layer-workflow.md](../skills/kbagent/references/semantic-layer-workflow.md) before starting: it carries the per-verb recipes, the rename cascade and the promote classification | `semantic-layer diff` (`--project-a/-b` or `--file-a/-b`) to confirm what a write would change | raw metastore REST (no rollback, no orphan scan, no modelUUID rewrite); a write with no export taken | | Share a semantic-layer object across projects, or make it org-wide | **ASK THE USER which project(s) before EVER passing `--scope organization\|targeted` (on `model create`/`add `) or running `scope set --scope organization`/`scope request-create` -- never guess or default to widening visibility.** Once the user names the target(s): `--scope targeted --target-project ALIAS\|ID` grants specific projects; `scope add\|remove --project P --type T --context-id ID --target-project ALIAS\|ID` adjusts an existing item's grants from the owning project (a merge), `scope set --target-project ...` / `--clear` replaces the list; org-wide needs `scope request-create` then an org-admin's `scope set --scope organization --yes` (preview with `--dry-run`) -- **irreversible, no downgrade endpoint, destructive-class**. `add ` without `--scope` inherits its model's scope. `scope get --type T --context-id ID` shows the current state first. See [metastore-scope-workflow.md](../skills/kbagent/references/metastore-scope-workflow.md) | `scope request-list --type T` to find items an org-admin should act on | passing `--scope organization\|targeted` or running `scope set --scope organization` without the user having named the target project(s) first; assuming `--scope organization` succeeds on a non-admin token (it 403s -- creating directly at organization scope needs the SAME organization-admin role as elevating) | | User asks to "log in" / authenticate via browser / register a session's projects | ATTENDED session + a background shell: run `kbagent auth login --device-code --stack URL --register-projects` in a **BACKGROUND** shell (capture stdout+stderr, human mode -- `--json` puts the panel on stderr), relay the verification URL + user code to the user, then poll `kbagent --json auth status` (exit 0 = signed in, exit 3 = not yet). The human's part is approving in the browser, not typing the command. No background shell -> hand the plain `auth login --stack URL --register-projects` to the user's terminal. To register projects from an EXISTING session, `kbagent auth register-projects --all` or `--project-id ID` is non-interactive and agent-safe | -- | running `auth login` in a FOREGROUND tool shell (~120 s timeout kills it mid-flight); running it unattended (nobody can approve); re-running it blind without checking `auth status` first (orphans a session); the flagless `register-projects` picker unattended; reading the token out of `auth.json`; using a numeric project id as an alias | @@ -252,7 +252,7 @@ its absence is NOT a promise the entry is version-independent (see §1 Rule 6). - A 401 whose body names something other than a bad credential now raises `AUTH_REJECTED`; the semantic-layer family's opaque metastore 401 reclassified to `MISSING_MASTER_TOKEN` on a write against a non-admin - token (see its matrix row) -- since PSGO-282 (vNEXT) a plain read never + token (see its matrix row) -- since PSGO-282 (0.97.0) a plain read never hits this at all, any valid token passes. Exit code is unchanged (3) for all three -- code checking `error.code == "INVALID_TOKEN"` alone still misses two real cases; accept all three before concluding the credential diff --git a/plugins/kbagent/skills/kbagent/references/commands-reference.md b/plugins/kbagent/skills/kbagent/references/commands-reference.md index 1ad66b73..460397a4 100644 --- a/plugins/kbagent/skills/kbagent/references/commands-reference.md +++ b/plugins/kbagent/skills/kbagent/references/commands-reference.md @@ -406,7 +406,7 @@ MFA login sends `challenge: SOFTWARE_TOKEN_MFA` explicitly to fix a 404 on perso ## Semantic Layer (Metastore) -Reads `KBAGENT_SERVE_URL` + `KBAGENT_SERVE_TOKEN` env vars. The scheduler auto-injects these (plus `KBAGENT_CONFIG_DIR`) into every AI-agent / `cli_command` subprocess. Outside a serve subprocess context the command refuses to run with exit code 2. **Inside a scheduled-agent task, prefer `kbagent http get /openapi.json` then a typed call over forking another `kbagent` CLI -- the HTTP path always sees the operator's live config (not the global `~/.config/keboola-agent-cli/` one).** -Manage Keboola metastore models -- datasets, metrics, relationships, constraints, glossary terms. Metastore URL derived from the stack URL by replacing `connection.` with `metastore.` (cloud/region-agnostic). Auth: same `X-StorageApi-Token` as Storage -- **reads work with any valid, non-disabled, non-expired token** (vNEXT, PSGO-282); **writes** still need a project-admin token (master, or any other admin-role user token) -- a non-admin token gets the opaque 401 `Failed to create project scope` on a write, which kbagent reclassifies to `MISSING_MASTER_TOKEN` with the remedy (since 0.92.0, #711; see [gotchas.md](gotchas.md)). Hidden alias: `kbagent sl ...` is equivalent to `kbagent semantic-layer ...`. See [semantic-layer-workflow.md](semantic-layer-workflow.md) for full recipes. +Manage Keboola metastore models -- datasets, metrics, relationships, constraints, glossary terms. Metastore URL derived from the stack URL by replacing `connection.` with `metastore.` (cloud/region-agnostic). Auth: same `X-StorageApi-Token` as Storage -- **reads work with any valid, non-disabled, non-expired token** (0.97.0, PSGO-282); **writes** still need a project-admin token (master, or any other admin-role user token) -- a non-admin token gets the opaque 401 `Failed to create project scope` on a write, which kbagent reclassifies to `MISSING_MASTER_TOKEN` with the remedy (since 0.92.0, #711; see [gotchas.md](gotchas.md)). Hidden alias: `kbagent sl ...` is equivalent to `kbagent semantic-layer ...`. See [semantic-layer-workflow.md](semantic-layer-workflow.md) for full recipes. - `semantic-layer model list --project P` -- list all models in a project. Output: `{models: [{id, name, sql_dialect, description}, ...]}`. Use to disambiguate when `--model` is required and the project has more than one model. - `semantic-layer model create --project P --name N [--description D] [--sql-dialect Snowflake]` -- create a new model. `--sql-dialect` defaults to `Snowflake`. Returns the new model UUID; subsequent commands accept either name or UUID via `--model`. @@ -440,7 +440,7 @@ Manage Keboola metastore models -- datasets, metrics, relationships, constraints ### Scope / target-project grants / elevation (`scope` sub-app, PSGO-140) -*(since vNEXT)* Every semantic-layer item carries a visibility `scope`: `project` (owner project only), `organization` (every project in the org), or `targeted` (owner + an explicit target-project grant list). `--scope`/`--target-project` are accepted by `model create` and every `add ` command above. With `--scope` omitted, `model create` makes a `project` model and `add ` **inherits its model's scope and target projects**. `--target-project` takes a registered alias or a numeric project ID (repeatable or comma-separated; an alias must be on the owner's stack) and needs `--scope targeted` (else exit 2). With `--scope targeted` and no `--target-project`: on a real terminal an interactive checkbox picker runs over the other projects on the stack; in `--json`/non-interactive mode it fails fast with `INVALID_ARGUMENT` (exit 2). **Always ask the user which project(s) before passing `--scope organization|targeted`** -- see `keboola-expert.md`'s scope rule; `--scope organization` is irreversible and gated as destructive. `edit`, `import --overwrite` and `promote` update the item in place (PUT), so an organization/targeted item keeps its scope, grants, id and pending elevation request. +*(since 0.97.0)* Every semantic-layer item carries a visibility `scope`: `project` (owner project only), `organization` (every project in the org), or `targeted` (owner + an explicit target-project grant list). `--scope`/`--target-project` are accepted by `model create` and every `add ` command above. With `--scope` omitted, `model create` makes a `project` model and `add ` **inherits its model's scope and target projects**. `--target-project` takes a registered alias or a numeric project ID (repeatable or comma-separated; an alias must be on the owner's stack) and needs `--scope targeted` (else exit 2). With `--scope targeted` and no `--target-project`: on a real terminal an interactive checkbox picker runs over the other projects on the stack; in `--json`/non-interactive mode it fails fast with `INVALID_ARGUMENT` (exit 2). **Always ask the user which project(s) before passing `--scope organization|targeted`** -- see `keboola-expert.md`'s scope rule; `--scope organization` is irreversible and gated as destructive. `edit`, `import --overwrite` and `promote` update the item in place (PUT), so an organization/targeted item keeps its scope, grants, id and pending elevation request. - `semantic-layer scope get --project P --type model|dataset|metric|relationship|constraint|glossary --context-id ID` -- show an item's current `scope`, `target_project_ids`, and any pending `scope_elevation_requested_at`. - `semantic-layer scope add|remove --project P --type T --context-id ID --target-project ALIAS|ID [--target-project ...]` -- attach / detach target projects of a `--scope targeted` item (alias or numeric ID; repeatable or comma-separated). A client-side **merge** (read the grants, apply the delta, PUT) -- **not atomic** against a concurrent grant change. Refused (exit 2) from a project that does not own the item -- the server hides the grants from a non-owner, so use `scope set --target-project` there. diff --git a/plugins/kbagent/skills/kbagent/references/gotchas.md b/plugins/kbagent/skills/kbagent/references/gotchas.md index ed85fb17..16413626 100644 --- a/plugins/kbagent/skills/kbagent/references/gotchas.md +++ b/plugins/kbagent/skills/kbagent/references/gotchas.md @@ -194,7 +194,7 @@ Versioning convention: check `kbagent project info` -> `is_master_token`, and register a project-admin token (`kbagent project edit --token ...`). Do NOT escalate this one to support -- it is by design. - - **(updated vNEXT -- PSGO-282, go-monorepo#596):** the "every call" + - **(updated 0.97.0 -- PSGO-282, go-monorepo#596):** the "every call" part above is now history. The Metastore no longer requires a master token for a plain read (`show`, `model list`, `search-context`, `get-context`, `validate`, `diff`, `scope get`, `scope request-list`) -- @@ -1181,7 +1181,7 @@ record per member. Consequences an agent must internalize: and `set` the whole thing back. There is no per-member endpoint. - **It uses the metastore's real `PUT`** (revisioned update, `meta.revision` increments, history preserved) when a record for that dimension already - exists — as `edit metric|…` does since vNEXT (before, those used DELETE+POST). + exists — as `edit metric|…` does since 0.97.0 (before, those used DELETE+POST). A brand-new dimension is `POST`-ed. - **The envelope `name` is the dimension, unique per project per type, so the `set`/`get` lookup is project-wide.** Because the dimension name is the @@ -1609,7 +1609,7 @@ events and emits a final `done` SSE frame mirroring the same record. - `kbagent semantic-layer edit metric --new-name NEW` updates the metric and ALSO every constraint whose `metrics[]` referenced the old name - (`metrics[]` rewritten to the new name). *(updated vNEXT)* Each is an + (`metrics[]` rewritten to the new name). *(updated 0.97.0)* Each is an in-place `PUT` -- same id, scope, grants and revision history, and a failed update changes nothing; before, every "edit" was a delete-then-create with a rollback re-POST. @@ -1635,7 +1635,7 @@ events and emits a final `done` SSE frame mirroring the same record. recommend running `semantic-layer validate` immediately. - **Partial-state envelope signal (updated v0.41.10 -- closes #294)**: the cascade is per-item only (each constraint is updated on its own; - *since vNEXT* a PUT, so there is nothing to roll back), NOT whole-operation atomicity. If the + *since 0.97.0* a PUT, so there is nothing to roll back), NOT whole-operation atomicity. If the metric rename succeeds but M of N dependent constraints fail to repoint, the response envelope sets `partial_state: true` and `recovery_hint: ""` @@ -5750,7 +5750,7 @@ workspaces". ## Metastore no longer requires a master token for reads (PSGO-282) -*(since vNEXT)* Every `semantic-layer` read (`show`, `model list`, +*(since 0.97.0)* Every `semantic-layer` read (`show`, `model list`, `search-context`, `get-context`, `validate`, `diff`, `scope get`, `scope request-list`) works with any valid, non-disabled, non-expired Storage token -- programmatic (`kbc_at_*`/`kbc_pat_*`) included. Before this, the metastore @@ -5777,7 +5777,7 @@ authorization failure: ## Metastore `scope` / `--target-project` / `scope set --scope organization` (PSGO-140) -*(since vNEXT)* `model create` / `add ` gained `--scope +*(since 0.97.0)* `model create` / `add ` gained `--scope project|organization|targeted` + `--target-project ALIAS|ID ...`, and a new `semantic-layer scope ` sub-app (also `GET/PUT /semantic-layer/scope/...` over `kbagent serve`). See diff --git a/plugins/kbagent/skills/kbagent/references/metastore-scope-workflow.md b/plugins/kbagent/skills/kbagent/references/metastore-scope-workflow.md index ed15e43a..42fc7346 100644 --- a/plugins/kbagent/skills/kbagent/references/metastore-scope-workflow.md +++ b/plugins/kbagent/skills/kbagent/references/metastore-scope-workflow.md @@ -19,7 +19,7 @@ override (a project-admin token that is not an org admin must pass `--scope project` under an org-level model, or it gets a 403). For one-line command reference, see -[commands-reference.md](commands-reference.md#scope--target-project-grants--elevation-scope-sub-app-since-vnext-psgo-140). +[commands-reference.md](commands-reference.md#scope--target-project-grants--elevation-scope-sub-app-psgo-140). For the schema-version / replace-vs-merge / 403-vs-404 surprises, see [gotchas.md](gotchas.md). diff --git a/plugins/kbagent/skills/kbagent/references/semantic-layer-workflow.md b/plugins/kbagent/skills/kbagent/references/semantic-layer-workflow.md index 8124b9dd..0ec87bd1 100644 --- a/plugins/kbagent/skills/kbagent/references/semantic-layer-workflow.md +++ b/plugins/kbagent/skills/kbagent/references/semantic-layer-workflow.md @@ -5,7 +5,7 @@ of datasets, metrics, relationships, constraints, and glossary terms. It is served from a separate API at `metastore.` (derived from `connection.` by string-substitution; cloud/region-agnostic). Auth is the same `X-StorageApi-Token` as Storage: **reads work with any valid, -non-disabled, non-expired token** (vNEXT, PSGO-282); **writes** (add / +non-disabled, non-expired token** (0.97.0, PSGO-282); **writes** (add / edit / remove / import / promote / build / scope add|remove|set) still need a project-admin token -- a master token qualifies, but so does any other project-admin user's token. Before PSGO-282, the metastore's auth gate diff --git a/pyproject.toml b/pyproject.toml index d8d6cc8b..870b2e1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "keboola-cli" -version = "0.96.2" +version = "0.97.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 4b5c8645..3e7e0a53 100644 --- a/src/keboola_agent_cli/changelog.py +++ b/src/keboola_agent_cli/changelog.py @@ -26,6 +26,22 @@ # Ordered newest-first. Each value is a list of brief one-line descriptions. CHANGELOG: dict[str, list[str]] = { + "0.97.0": [ + "New (#715): semantic-layer items can now be shared with chosen projects or with the " + "whole organization, not only kept in the owning project (AI-3790). `model create` and " + "every `add ` take `--scope project|targeted|organization` and `--target-project` " + "(a registered alias or a project ID). The new `semantic-layer scope` command group " + "shows and changes who sees an existing item: `get`, `add`, `remove` and `set`, plus " + "`request-create`, `request-delete` and `request-list` for requests to make an item " + "organization-wide. Without `--scope`, `model create` keeps the model in its project, " + "and `add ` uses the scope of its model. Making an item organization-wide needs " + "the organization-admin role and cannot be reverted, so `--deny-destructive` blocks it. " + "`kbagent serve` has the same functions under `/semantic-layer/scope`.", + "Fix (#715): `semantic-layer edit`, `import --overwrite` and `promote` now update an item " + "in place, so it keeps its ID, its visibility and its history. Before, they deleted the " + "item and created it again with a new ID. A failed update changes nothing, so the " + "`rollback` field in the result is now always `null`.", + ], "0.96.2": [ "Fix (#825): the `data-app` commands now label the app link `Open the app:` and the " "Keboola UI link `Configuration:`, so it is clear which link opens what. Before, the " diff --git a/uv.lock b/uv.lock index 21a031e7..5062fd4e 100644 --- a/uv.lock +++ b/uv.lock @@ -465,7 +465,7 @@ wheels = [ [[package]] name = "keboola-cli" -version = "0.96.2" +version = "0.97.0" source = { editable = "." } dependencies = [ { name = "croniter" },