From 625e9979af4502d1a3ed288ac893991f232e17f4 Mon Sep 17 00:00:00 2001 From: soustruh Date: Mon, 12 Oct 2026 13:09:24 +0200 Subject: [PATCH] chore(release): 0.99.0 --- .claude-plugin/marketplace.json | 2 +- CLAUDE.md | 6 +-- README.md | 2 +- docs/web-server.md | 10 ++-- plugins/kbagent/.claude-plugin/plugin.json | 2 +- plugins/kbagent/agents/keboola-expert.md | 6 +-- .../kbagent/references/auth-workflow.md | 2 +- .../kbagent/references/commands-reference.md | 24 ++++----- .../kbagent/references/data-app-workflow.md | 6 +-- .../skills/kbagent/references/gotchas.md | 44 ++++++++-------- .../kbagent/references/oauth-workflow.md | 2 +- .../kbagent/references/scaffold-workflow.md | 2 +- .../references/storage-types-workflow.md | 4 +- .../kbagent/references/sync-workflow.md | 10 ++-- .../kbagent/references/workspace-workflow.md | 2 +- pyproject.toml | 2 +- src/keboola_agent_cli/changelog.py | 50 +++++++++++++++++++ src/keboola_agent_cli/commands/context.py | 16 +++--- src/keboola_agent_cli/permissions.py | 4 +- src/keboola_agent_cli/server/dependencies.py | 2 +- .../services/_data_app_bodies.py | 2 +- uv.lock | 2 +- 22 files changed, 126 insertions(+), 76 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index a1e99447..2642cf10 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "plugins": [ { "name": "kbagent", - "version": "0.98.0", + "version": "0.99.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 e68e9321..3050fb52 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -667,7 +667,7 @@ kbagent storage truncate-table --project NAME --table-id ID [--table-id ...] [-- kbagent storage delete-column --project NAME --table-id ID --column COL [--column ...] [--force] [--dry-run] [--yes] [--branch ID] kbagent storage delete-bucket --project NAME --bucket-id ID [--bucket-id ...] [--force] [--dry-run] [--yes] [--branch ID] kbagent storage swap-tables --project NAME --table-id ID --target-table-id ID --branch ID [--dry-run] [--yes] [--timeout SECONDS] - # --timeout SECONDS on create-table and swap-tables (since vNEXT, #713): both halves + # --timeout SECONDS on create-table and swap-tables (since 0.99.0, #713): both halves # of the repartition path move real data yet used to inherit the 60s metadata-job # budget, so a large BigQuery table reported STORAGE_JOB_TIMEOUT for a job that was # still running and would succeed. Default is now 300s on both. A local timeout @@ -825,7 +825,7 @@ kbagent token refresh --project NAME --token-id ID [--yes] # is persisted. `permissions show` / `kbagent doctor` also generalized: they now flag ANY # persisted pattern matching zero known operations, not only the retired tool:* namespace. # PermissionEngine itself stays lenient at evaluation time -- only `permissions set` is strict. -# (since vNEXT, #655) The policy also firewalls the WHOLE `kbagent serve` REST surface: every +# (since 0.99.0, #655) The policy also firewalls the WHOLE `kbagent serve` REST surface: every # route is classified in server/route_permissions.py and checked by one app-level dependency # against the SERVED config dir's policy -> HTTP 403 error_code PERMISSION_DENIED, the same # code the CLI exits on. On 0.98.0 and older only /auth/* (0.90.1+) and /merge-requests/* @@ -841,7 +841,7 @@ kbagent token refresh --project NAME --token-id ID [--yes] # the handlers of scope PUT, models, items/{kind} (per-kind leaf key), import, promote # and build check ` --scope organization` (FLAG_ESCALATIONS) for a typed OR an # inherited organization scope, like the CLI. -# `GET /permissions/show` (serve, since vNEXT) reports the EFFECTIVE policy (persisted block +# `GET /permissions/show` (serve, since 0.99.0) reports the EFFECTIVE policy (persisted block # merged with the daemon's --deny-* flags); read-only by design -- no REST route can widen # the policy that constrains it, so `permissions set|reset` stay terminal actions on the host. kbagent permissions list [--category read|write|destructive|admin] diff --git a/README.md b/README.md index 69ac2b48..70e46198 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ On Windows, run the PowerShell install from the Windows section above as the sec If `uv tool list` shows `keboola-cli` next to `keboola-agent-cli`, the update already worked. kbagent runs from `keboola-cli` and the old entry does no harm. Do not remove the old entry on its own: `uv tool uninstall keboola-agent-cli` also deletes the `kbagent` executable that `keboola-cli` uses, and the shell then reports `command not found`. To restore the executable, run `uv tool install --force --reinstall "keboola-cli[server] @ https://github.com/keboola/cli/releases/download/v/keboola_cli--py3-none-any.whl"` with the latest version number from the [releases page](https://github.com/keboola/cli/releases/latest). -A failed startup update prints the last line of the uv output as `Cause:` (since vNEXT), then the recovery command. +A failed startup update prints the last line of the uv output as `Cause:` (since 0.99.0), then the recovery command. ## Web UI (optional) diff --git a/docs/web-server.md b/docs/web-server.md index 2d88b4dc..bdfa8998 100644 --- a/docs/web-server.md +++ b/docs/web-server.md @@ -105,7 +105,7 @@ read/audit half of `kbagent auth` *(since v0.90.1)* — `login` / Going the other way, several CLI surfaces are deliberately CLI-only: `sync` (filesystem-local by design), `init`, and the *write* half of `permissions` (`set` / `reset`). The mirrors still considered missing are tracked in #657. -`GET /permissions/show` *(since vNEXT)* is the read half — see "The session +`GET /permissions/show` *(since 0.99.0)* is the read half — see "The session firewall applies to every route" below. Auto-generated OpenAPI spec at `/openapi.json`, Swagger UI at `/docs`. @@ -486,7 +486,7 @@ including the `kbc-session://` sentinel. `/auth/*` was the **first router to enforce the permission policy** (0.90.1): every route above declares `Depends(require_permission(...))` at its own call -site. Since vNEXT that is no longer special — *every* route on the server is +site. Since 0.99.0 that is no longer special — *every* route on the server is enforced (see "The session firewall applies to every route" below) — and the three inline declarations survive only as the per-route override form. @@ -501,7 +501,7 @@ you reach for a flag: classified `admin`, and `--deny-writes` appends `cli:write`, which spans write + destructive + admin — so the CLI callback blocks the `serve` command itself (exit code 6, `Operation 'serve' is blocked by the active permission - policy`). `--deny-destructive` does start the server, and since vNEXT it + policy`). `--deny-destructive` does start the server, and since 0.99.0 it blocks every destructive route (no `/auth/*` operation is destructive). - **Use a persisted policy instead.** Run, on the host, in a real terminal (`permissions set` requires a typed confirmation code — there is no `--yes`): @@ -576,14 +576,14 @@ for `serve` to use. ### The session firewall applies to every route -*(since vNEXT — issue #655)* +*(since 0.99.0 — issue #655)* `PermissionEngine` used to be built only in the Typer callback, so a persisted `permissions set --mode deny` policy — and both `--deny-writes` and `--deny-destructive` — protected the CLI process and nothing else. `kbagent serve` exposed every route, `DELETE /storage/buckets` included, behind a single all-or-nothing bearer token. 0.90.1 closed that for `/auth/*` and 0.94.0 for -`/merge-requests/*`; vNEXT closes it for the whole surface. +`/merge-requests/*`; 0.99.0 closes it for the whole surface. **How it works.** One app-level dependency (`server/route_permissions.py`) runs on every request, looks the matched route up by `(method, path template)` in diff --git a/plugins/kbagent/.claude-plugin/plugin.json b/plugins/kbagent/.claude-plugin/plugin.json index 2310dc35..b434cd6b 100644 --- a/plugins/kbagent/.claude-plugin/plugin.json +++ b/plugins/kbagent/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "kbagent", - "version": "0.98.0", + "version": "0.99.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 7e49e460..649fb9f9 100644 --- a/plugins/kbagent/agents/keboola-expert.md +++ b/plugins/kbagent/agents/keboola-expert.md @@ -121,7 +121,7 @@ been retired, so its absence is NOT a promise (see §1 Rule 6). | Create typed table with native types | `kbagent storage create-table --column pk:VARCHAR(40) --column amount:NUMBER(18,2) --not-null pk --default amount=0` | -- | re-creating via raw REST to `tables-definition` | | Add one column to an existing table | `kbagent storage add-column --project P --table-id in.c-foo.data --column status:VARCHAR(20) [--not-null] [--default active]` -- synchronous, same `name:TYPE(length)` grammar as `create-table` | -- | re-creating the whole table to add a field (loses data / PK / dependents) | | Promote a rebuilt table into the original name | `kbagent storage swap-tables --project P --table-id in.c-foo.data --target-table-id in.c-foo.data_new --branch --yes` -- async `tableSwap`; the service refuses without a branch (any branch, prod included) | -- | renaming or delete + re-upload (loses history; every downstream config needs rewriting) | -| Repartition / recluster a populated BigQuery table | `kbagent storage create-table --source-table-id --time-partitioning-type DAY --time-partitioning-field created_at --clustering-field tenant_id` (BigQuery only) to copy the data into the new layout, then `swap-tables` to flip it in. `--source-table-id` derives the schema, so `--column` is forbidden. Range partitioning: all four `--range-partitioning-*` flags together. VERIFY the result with `storage table-detail --json` -> `.definition.timePartitioning` / `.clustering` (0.88.0+) -- `create-table` only echoes the layout you REQUESTED, so it proves nothing. Both steps take `--timeout SECONDS` (vNEXT+, default 300); a `STORAGE_JOB_TIMEOUT` (exit 4, `retryable: false`) means kbagent stopped WATCHING, not that the job failed -- follow it with `storage job-detail --job-id ID --wait`, never re-run: a repeated swap swaps the tables back, a repeated create starts a second copy. See [storage-types-workflow.md](../skills/kbagent/references/storage-types-workflow.md) | -- | `CREATE TABLE ... AS SELECT` in a workspace (drops NOT NULL + primary key) | +| Repartition / recluster a populated BigQuery table | `kbagent storage create-table --source-table-id --time-partitioning-type DAY --time-partitioning-field created_at --clustering-field tenant_id` (BigQuery only) to copy the data into the new layout, then `swap-tables` to flip it in. `--source-table-id` derives the schema, so `--column` is forbidden. Range partitioning: all four `--range-partitioning-*` flags together. VERIFY the result with `storage table-detail --json` -> `.definition.timePartitioning` / `.clustering` (0.88.0+) -- `create-table` only echoes the layout you REQUESTED, so it proves nothing. Both steps take `--timeout SECONDS` (0.99.0+, default 300); a `STORAGE_JOB_TIMEOUT` (exit 4, `retryable: false`) means kbagent stopped WATCHING, not that the job failed -- follow it with `storage job-detail --job-id ID --wait`, never re-run: a repeated swap swaps the tables back, a repeated create starts a second copy. See [storage-types-workflow.md](../skills/kbagent/references/storage-types-workflow.md) | -- | `CREATE TABLE ... AS SELECT` in a workspace (drops NOT NULL + primary key) | | Re-seed a table without losing schema / PK / dependents | `kbagent storage truncate-table --project P --table-id in.c-foo.data [--dry-run] [--yes]` -- rows only, uniformly async-via-job on every branch; batch via repeated `--table-id` | -- | drop + recreate (loses descriptions, PK, sharing edges, and breaks every downstream reference); deleting rows via raw SQL in a workspace (bypasses the Storage audit trail) | | Back up / restore a table around a risky change | `kbagent storage snapshot-create --table-id ...` then, to restore, `kbagent storage table-from-snapshot --snapshot-id ID --bucket-id B --name NEW` -- restore is always a NEW table (`--name` REQUIRED, no overwrite): verify it, then `swap-tables`. See [snapshot-workflow.md](../skills/kbagent/references/snapshot-workflow.md) | `storage snapshots` / `snapshot-detail` to find one | exporting to CSV as a "backup" (loses column types + PK); `create-table --snapshot-id` (not a thing) | | Debug a failed job | `kbagent job detail --project P --job-id J --log-tail-lines 200 --json` | `kbagent workspace from-transformation` for SQL repro (on a dev branch it reads the branch's transformation only since 0.96.1, #807; older versions load production's input mapping) | "I think the issue is..." without reading logs; a new job run only to see its logs (a writer can send its data again) | @@ -307,7 +307,7 @@ its absence is NOT a promise the entry is version-independent (see §1 Rule 6). branch's tree -- `sync pull` to re-target, never push. Since 0.96.1 (#792) `sync push` deletes only with `--force` (else `skipped_deletions`), and a `- REMOTE DELETED` diff line = deleted on the remote, push never re-creates - it: `sync pull`, or `config restore` to keep it. vNEXT+: a `CONFLICT` on a + it: `sync pull`, or `config restore` to keep it. 0.99.0+: a `CONFLICT` on a file with a config id and no manifest entry = no baseline: keep the edit aside, `sync pull --theirs`, re-apply, push (sync-workflow.md). An `ENCRYPTION_FAILED` push wrote nothing: fix the cause, push again. `sync @@ -372,7 +372,7 @@ its absence is NOT a promise the entry is version-independent (see §1 Rule 6). grep rules nothing out). `create` defaults it ON at **0.87.0+**; <= 0.86.0 patch + redeploy. - **Managed-repo app `running` with no `WORKSPACE_ID`** (`data-app detail` - shows `Git: {}`): vNEXT+ `deploy` backfills `parameters.dataApp.git`; pass + shows `Git: {}`): 0.99.0+ `deploy` backfills `parameters.dataApp.git`; pass `--git-branch` with the branch the code is on (an app with runs refuses without it). Older: merge the block by hand, redeploy. data-app-workflow.md. - **`data-app password` keeps the password out of the chat (0.96.1+)**: it diff --git a/plugins/kbagent/skills/kbagent/references/auth-workflow.md b/plugins/kbagent/skills/kbagent/references/auth-workflow.md index 8717957b..eb90c3a3 100644 --- a/plugins/kbagent/skills/kbagent/references/auth-workflow.md +++ b/plugins/kbagent/skills/kbagent/references/auth-workflow.md @@ -138,7 +138,7 @@ has an account and should create the project in the Keboola UI instead. redirect actually arrives, there is no fallback -- an error past that point is terminal (e.g. a `state` mismatch, or the exchange itself failing) and login simply fails; re-run the command. - - Before the browser wait starts, the CLI prints a notice (since vNEXT; + - Before the browser wait starts, the CLI prints a notice (since 0.99.0; to stderr with `--json`): it waits up to 115 s for the browser to return. If the browser shows the project list and not "Login complete", the sign-in cannot return: press Ctrl+C and run the diff --git a/plugins/kbagent/skills/kbagent/references/commands-reference.md b/plugins/kbagent/skills/kbagent/references/commands-reference.md index 18cb6c5d..1060242f 100644 --- a/plugins/kbagent/skills/kbagent/references/commands-reference.md +++ b/plugins/kbagent/skills/kbagent/references/commands-reference.md @@ -75,10 +75,10 @@ end-to-end walkthrough and troubleshooting. ## Project Management - `project create --url URL [--project ALIAS] [--name NAME] [--backend snowflake|bigquery] [--sync-backend-init]` -- create a **brand-new** Keboola project from a machine with no Keboola identity: no account, no token, no `auth login` first. The only kbagent command that works from nothing. In one call it provisions the project, stores the returned project-pinned session in `auth.json`, and registers the project in `config.json` under the `kbc-session://` sentinel -- becoming the default project when nothing else was registered, so the next command needs no `--project`. **The project it creates is owned by nobody**: the result's `confirm_url` is a single-use, days-limited link a human must open and sign in at to take ownership -- relay it verbatim, an unclaimed project is a billable orphan. `auth status` re-prints it as `agent_confirm_url` while it is pending. Confirming **revokes this session**, so the next step is always `kbagent auth login --stack URL`; the alias survives it, because the sentinel keys on project id + stack and not on the session. Requires the `agent-provisioning` stack feature (`STACK_FEATURES__AGENT_PROVISIONING`), which is off on most stacks -- the command is always registered, so seeing it in `--help` proves nothing; without the feature the stack 404s and the command exits 1 with `AUTH_NOT_SUPPORTED_ON_STACK`, naming the flag and how to connect an existing project instead. Refuses with exit 5 when a session for that stack already exists (one session per stack; whoever has one has an account and should use the Keboola UI). `--backend` omitted keeps the stack maintainer's default; `--sync-backend-init` waits for backend init instead of the async default, which warns that the first Storage command may fail until it lands (since 0.95.0) -- `project add --project NAME --url URL [--token-stdin | --token-file PATH | --token-env NAME | --token TOKEN]` -- connect a project (token verified via API). Prefer `--token-stdin` (hidden prompt on a TTY, reads a pipe otherwise); `--token-file PATH` reads a file and deletes it once the command succeeds (`--keep-token-file` keeps it); `--token-env NAME` reads the variable you name, for CI. `--token VALUE` writes the token into the shell history, the REPL history file and a process listing, so it warns when used on a terminal. The sources are mutually exclusive (exit 2). `KBC_TOKEN` is still read implicitly here (since vNEXT) +- `project add --project NAME --url URL [--token-stdin | --token-file PATH | --token-env NAME | --token TOKEN]` -- connect a project (token verified via API). Prefer `--token-stdin` (hidden prompt on a TTY, reads a pipe otherwise); `--token-file PATH` reads a file and deletes it once the command succeeds (`--keep-token-file` keeps it); `--token-env NAME` reads the variable you name, for CI. `--token VALUE` writes the token into the shell history, the REPL history file and a process listing, so it warns when used on a terminal. The sources are mutually exclusive (exit 2). `KBC_TOKEN` is still read implicitly here (since 0.99.0) - `project list` -- list all connected projects (tokens masked). Carries `auth_mode` on every `--json` entry and an `Auth` column (before `Token`) in the human table: exactly `session` (browser login) or `static` (Storage token), **always present and never empty** -- including on a row synthesized for a project whose connection failed -- so branch on it instead of testing for absence. This is how you answer "which mode is this project in": `kbagent project list --json | jq '.data[].auth_mode'`. In the human table a session project's `Token` cell is a dash, because the stored sentinel is not a credential and masked (`kbc-...9840`) it reads like a truncated real token; the `--json` `token` value is unchanged (`mask_token(...)`, a stable contract) and the sentinel's body is the project id, which has its own column (since v0.80.0) - `project remove --project NAME` -- disconnect a project -- `project edit --project NAME [--url URL] [--token-stdin | --token-file PATH | --token-env NAME | --token TOKEN] [--new-alias NEW] [--dry-run]` -- update connection details and/or rename the alias. `--new-alias` cascades through config.json (`projects` key + `default_project` if matched) and the nested sync directory `//` when present (-2 collision suffix, git-mv with shutil fallback). Lineage cache rebuild is manual (see gotchas, since v0.31.0). Combined with `--url` / `--token` in one call, those mutations target the new alias post-rename. `--dry-run` previews everything (collision check, planned disk-rename method, lineage-cache warning) without mutating state -- same exit codes as live for validation errors. `--token` on a browser-login (session) project is allowed and converts it to a static-token project, but warns that `auth logout --remove-projects` will no longer clean that alias up (`project remove` does); `--json` carries the text in an additive top-level `warnings` array, and `--dry-run` previews the identical warning (since v0.80.0). The same four token sources as `project add`, with two deliberate differences: this command never prompts when no token is given (the token is optional here), and it does NOT read `KBC_TOKEN` -- a new token rewrites the alias's `project_id` and `project_name` from whatever it verifies as, so an exported variable could silently repoint the alias at another project during a plain `--new-alias`. `--token-file` is deleted only after the edit succeeds, never on `--dry-run` and never on a failure (since vNEXT) +- `project edit --project NAME [--url URL] [--token-stdin | --token-file PATH | --token-env NAME | --token TOKEN] [--new-alias NEW] [--dry-run]` -- update connection details and/or rename the alias. `--new-alias` cascades through config.json (`projects` key + `default_project` if matched) and the nested sync directory `//` when present (-2 collision suffix, git-mv with shutil fallback). Lineage cache rebuild is manual (see gotchas, since v0.31.0). Combined with `--url` / `--token` in one call, those mutations target the new alias post-rename. `--dry-run` previews everything (collision check, planned disk-rename method, lineage-cache warning) without mutating state -- same exit codes as live for validation errors. `--token` on a browser-login (session) project is allowed and converts it to a static-token project, but warns that `auth logout --remove-projects` will no longer clean that alias up (`project remove` does); `--json` carries the text in an additive top-level `warnings` array, and `--dry-run` previews the identical warning (since v0.80.0). The same four token sources as `project add`, with two deliberate differences: this command never prompts when no token is given (the token is optional here), and it does NOT read `KBC_TOKEN` -- a new token rewrites the alias's `project_id` and `project_name` from whatever it verifies as, so an exported variable could silently repoint the alias at another project during a plain `--new-alias`. `--token-file` is deleted only after the edit succeeds, never on `--dry-run` and never on a failure (since 0.99.0) - `project status [--project NAME]` -- test connectivity and response time. Same guaranteed `auth_mode` field as `project list`, with the `Auth` column right after `Status` and present on ERROR rows too (since v0.80.0) - `project refresh --project ALIAS | --all [--dry-run] [--force] [--yes] [--token-description DESC] [--token-expires-in N]` -- mint replacement Storage tokens via the Manage API for projects whose token is expired or invalid. `--force` also replaces still-valid non-expiring tokens. Browser-login (session) projects land in `skipped` with the reason that there is no static token to replace -- their credential lives in `auth.json` and rotates on its own -- and `--force` does not convert them either; use `project edit --token-stdin` for a deliberate single-project conversion (since v0.80.0). **Since 0.98.0 (#745)** a non-empty `projects_failed` exits **1** (was 0); `--dry-run` exits **1** on a failure too - `project description-get --project NAME` -- read the dashboard project description (KBC.projectDescription on the default branch). Returns `{"description": ""}` if not set, not an error @@ -107,7 +107,7 @@ All seven commands authenticate via `KBC_MANAGE_API_TOKEN` (Manage API), not the ## Permissions (session firewall commands) The `permissions` subcommands persist a write/destructive policy to config.json (the `--deny-*` flags above are the one-shot form). The engine guards against agent mistakes; it is not a sandbox. -Since vNEXT (#655) the persisted policy also firewalls the whole `kbagent serve` REST surface -- every route is checked against the SERVED config dir's policy and a denial answers HTTP 403 `PERMISSION_DENIED`. On 0.98.0 and older only `/auth/*` (0.90.1+) and `/merge-requests/*` (0.94.0+) were checked. A `--scope organization` semantic-layer write (typed or inherited from the target model) is destructive-class over REST too, as on the CLI. `POST/PUT/DELETE /semantic-layer/items/{kind}` check the parent key and the per-kind leaf key (`semantic-layer.add.metric`), as on the CLI. `GET /permissions/show` (serve-only, since vNEXT) reports the EFFECTIVE policy; there is deliberately no REST route that CHANGES it. +Since 0.99.0 (#655) the persisted policy also firewalls the whole `kbagent serve` REST surface -- every route is checked against the SERVED config dir's policy and a denial answers HTTP 403 `PERMISSION_DENIED`. On 0.98.0 and older only `/auth/*` (0.90.1+) and `/merge-requests/*` (0.94.0+) were checked. A `--scope organization` semantic-layer write (typed or inherited from the target model) is destructive-class over REST too, as on the CLI. `POST/PUT/DELETE /semantic-layer/items/{kind}` check the parent key and the per-kind leaf key (`semantic-layer.add.metric`), as on the CLI. `GET /permissions/show` (serve-only, since 0.99.0) reports the EFFECTIVE policy; there is deliberately no REST route that CHANGES it. - `permissions list [--category read|write|destructive|admin]` -- list all operations with their risk category and current allowed/denied status - `permissions show` -- show the current active permission policy. *(since 0.91.0)* also flags ANY persisted pattern matching zero known operations, not only the retired `tool:*` namespace (same generalization applies to `kbagent doctor`) -- a typo like `cli:reed` or `stroage.upload-table` is surfaced too, with a generic "check for typos" hint instead of the MCP-migration one - `permissions set --mode allow|deny [--allow PATTERN ...] [--deny PATTERN ...]` -- set the permission policy (firewall rules); patterns like `cli:read`, `cli:write`, `cli:destructive`. `tool:*` patterns are INERT since v0.85.0 (the MCP passthrough is gone) -- they load but match nothing, so a `--mode deny` policy whose only allowance was `tool:read` now denies everything. *(since 0.91.0, issue #688)* Every pattern is validated BEFORE the interactive confirmation: it must be a `cli:*` category, an exact operation name, or a glob matching at least one known operation, or the call fails fast with `VALIDATION_ERROR` (exit 2, `error.details.invalid_patterns` in `--json`) and nothing is written @@ -147,7 +147,7 @@ Requires a **super-admin** Manage API token (same kind as `org setup`). Same def - `config delete --project NAME --component-id ID --config-id ID [--branch ID] [--dry-run]` -- SOFT-delete a configuration into the Storage trash (restorable). Since 0.89.0 it locates the config first and a config already in the trash is NOT deleted again -- the raw API purges permanently on a second DELETE (the retry-after-timeout trap); kbagent reports `already_in_trash` and exits 0 instead. `--dry-run` reports the located state without writing. Undo with `config restore`. Permission class `destructive` - `config restore --project NAME --component-id ID --config-id ID [--branch ID]` *(since v0.89.0)* -- restore a trashed configuration (versions, rows and metadata come back). Only works on a config currently in the trash. Permission class `write` - `config trash-list --project NAME [--component-id ID] [--branch ID]` *(since v0.89.0)* -- list configurations in the trash; each row carries `component_id`, `config_id`, `name`, `version` and `deleted_at`, which is exactly what `config restore` needs. Permission class `read` -- `config new --component-id ID [--project NAME] [--name NAME] [--output-dir DIR] [--push --no-files --description D --configuration JSON|@file|- --configuration-file PATH --no-validate --branch ID --dry-run --allow-plaintext-on-encrypt-failure]` -- **two modes**. **Default (no `--push`)**: scaffold new config from component schema; writes files to `--output-dir` or prints to stdout. **No Storage write calls** -- one AI Service read fetches the component schema/examples (that is what `--project` authenticates even without `--push`). **With `--push`** (0.33.0+, requires `--project` + non-empty `--name`): also POSTs to `/v2/storage/components/{cid}/configs` for a one-shot remote create. `#`-prefixed secrets in the pushed body auto-encrypt via the Encryption API first (fail-closed; since 0.54.0, #378; `--allow-plaintext-on-encrypt-failure` overrides). `--no-files` skips the filesystem step entirely (FIIA-style empty-shell pattern). `--configuration` / `--configuration-file` override the POSTed body (default is `{}`, with validation auto-skipped for the default empty shell). `--dry-run` previews the planned POST + validation result without creating. Since 0.89.0, `--push` + `--output-dir` writes the scaffold WITH `_keboola.config_id` into the subtree of the branch the config was created in (registering the branch in the manifest when missing), so the next `sync push` adopts it instead of duplicating (issue #644); with an explicit body the local file mirrors the pushed encrypted configuration. Since vNEXT (#792) the scaffold also records `_keboola.base_config_hash` (the created config as the API returned it), so `sync diff` compares it 3-way and push never overwrites an edit made in the UI (see `sync-workflow.md` > "Files with a config id and no manifest entry"). Schema validation runs by default when an explicit body is given (fail-closed: `ConfigError` exit 5 on mismatch) but skips silently if the AI Service has no schema for the component or returns an error; `--no-validate` opts out. Works for ALL component types including `keboola.snowflake-transformation`. **Since 0.90.0** the scaffold mode's optional `--project` really does fall back to a default project when omitted -- on <= 0.89.x it failed with `CONFIG_ERROR: Project 'None' not found` (see `gotchas.md`). **Since 0.93.0 (#684)** that fallback is the `project use` pin, not the first registered project. +- `config new --component-id ID [--project NAME] [--name NAME] [--output-dir DIR] [--push --no-files --description D --configuration JSON|@file|- --configuration-file PATH --no-validate --branch ID --dry-run --allow-plaintext-on-encrypt-failure]` -- **two modes**. **Default (no `--push`)**: scaffold new config from component schema; writes files to `--output-dir` or prints to stdout. **No Storage write calls** -- one AI Service read fetches the component schema/examples (that is what `--project` authenticates even without `--push`). **With `--push`** (0.33.0+, requires `--project` + non-empty `--name`): also POSTs to `/v2/storage/components/{cid}/configs` for a one-shot remote create. `#`-prefixed secrets in the pushed body auto-encrypt via the Encryption API first (fail-closed; since 0.54.0, #378; `--allow-plaintext-on-encrypt-failure` overrides). `--no-files` skips the filesystem step entirely (FIIA-style empty-shell pattern). `--configuration` / `--configuration-file` override the POSTed body (default is `{}`, with validation auto-skipped for the default empty shell). `--dry-run` previews the planned POST + validation result without creating. Since 0.89.0, `--push` + `--output-dir` writes the scaffold WITH `_keboola.config_id` into the subtree of the branch the config was created in (registering the branch in the manifest when missing), so the next `sync push` adopts it instead of duplicating (issue #644); with an explicit body the local file mirrors the pushed encrypted configuration. Since 0.99.0 (#792) the scaffold also records `_keboola.base_config_hash` (the created config as the API returned it), so `sync diff` compares it 3-way and push never overwrites an edit made in the UI (see `sync-workflow.md` > "Files with a config id and no manifest entry"). Schema validation runs by default when an explicit body is given (fail-closed: `ConfigError` exit 5 on mismatch) but skips silently if the AI Service has no schema for the component or returns an error; `--no-validate` opts out. Works for ALL component types including `keboola.snowflake-transformation`. **Since 0.90.0** the scaffold mode's optional `--project` really does fall back to a default project when omitted -- on <= 0.89.x it failed with `CONFIG_ERROR: Project 'None' not found` (see `gotchas.md`). **Since 0.93.0 (#684)** that fallback is the `project use` pin, not the first registered project. - `config clone --project P --component-id ID --config-id ID --name NAME [--target-project P2] [--description D] [--set PATH=VALUE ...] [--secret PATH=VALUE ...] [--branch ID] [--target-branch ID] [--dry-run] [--allow-plaintext-on-encrypt-failure]` (0.84.2+, #587) -- duplicate a configuration **whole**. Reach for this instead of reading `config detail` and rebuilding a body: copying only `configuration["parameters"]` silently drops its siblings (`runtime`, `storage`, `authorization`), and a lost `runtime.parallelism` makes Keboola fall back to `parallelism: 1` -- the reporter's 65-row writer went sequential, 140 min instead of ~60-90, with nothing in any output pointing at it. **Same project** (default): server-side copy via `POST .../configs/{id}/versions/{v}/create`; rows and `KBC::` encrypted values travel with it (verified live). `--set PATH=VALUE` is applied as a follow-up update on the copy, so an override can never be the reason a key went missing. **Cross project** (`--target-project`): reassembled client-side and rows recreated one by one, because encrypted values **cannot** travel -- a Keboola ciphertext is scoped to the project it was encrypted in. Any `KBC::` value makes the clone **fail with exit 5**, listing every path, until re-supplied via `--secret PATH=VALUE` (encrypted in the TARGET project on write). `--dry-run` reports those paths instead of refusing -- run it first to learn what to gather. Storage bucket/table IDs are copied **verbatim, never remapped**; `sync clone` is the command that remaps. - `config variables-set --project NAME --component-id ID --config-id ID --var KEY=VALUE [--var ...] [--replace] [--variables-id ID] [--values-id ID] [--branch ID] [--dry-run] [--allow-plaintext-on-encrypt-failure] [--yes]` -- attach variable values to a config. Auto-creates a sibling `keboola.variables` config + default row on first use and links it via the parent's `runtime.variables_id` / `variables_values_id`. Defaults to merge; `--replace` drops keys not in `--var`. `#`-prefixed values encrypt via the Encryption API (fail-closed; exit non-zero on `ENCRYPTION_FAILED`). See `variables-workflow.md` - `config variables-get --project NAME --component-id ID --config-id ID [--branch ID]` -- resolve `variables_id` + `values_id` from the parent config and fetch the current KEY=VALUE map. Returns `{linked: bool, variables_id, values_id, values}`; `linked=false` means the parent has no variables attached @@ -160,7 +160,7 @@ Requires a **super-admin** Manage API token (same kind as `org setup`). Same def - `config row-create --project NAME --component-id ID --config-id ID --name ROW_NAME [--description D] [--configuration JSON|@file|-] [--is-disabled] [--branch ID] [--allow-plaintext-on-encrypt-failure]` -- create a new configuration row. Returns the full row dict with `id`, `name`, `version`. Optional `--configuration` accepts JSON inline, `@file`, or stdin (`-`). `#`-prefixed secrets auto-encrypt before write (fail-closed; since 0.54.0, #378). - `config row-update --project NAME --component-id ID --config-id ID --row-id ID [--name N] [--description D] [--configuration JSON|@file|-] [--change-description TEXT] [--is-disabled | --is-enabled] [--branch ID] [--allow-plaintext-on-encrypt-failure]` -- update an existing configuration row. Pass only the fields you want to change; omitted fields are preserved. `--is-disabled` / `--is-enabled` toggle the row's enabled state. `--change-description` sets the new row version's `changeDescription` audit line (default: auto-generated). `#`-prefixed secrets auto-encrypt before write (fail-closed; since 0.54.0, #378). - `config row-delete --project NAME --component-id ID --config-id ID --row-id ID [--branch ID] [--yes]` -- delete a configuration row. Destructive (gated behind `--allow-destructive`). Branch-aware. Without `--yes` and outside `--json` mode, prompts for interactive confirmation; `--json` mode auto-skips the prompt. -- `config oauth-url --project NAME --component-id ID --config-id ID [--redirect-url URL] [--no-open]` -- return the OAuth authorization URL for a component that uses OAuth authentication. **Requires a master Storage API token** (canManageTokens privilege) -- non-master tokens fail with `MISSING_MASTER_TOKEN` exit 3 on a fail-fast pre-flight check before any HTTP write happens. (since vNEXT) the URL is opened in the default browser automatically in interactive human mode (never under `--json`, when stdout is not a terminal, or where `auth login` would not open a browser either: SSH session, container, WSL without a working `wslview`); `--no-open` prints it only. The `--json` result has `browser_opened: false`: never tell the user the link is open. The URL is also printed unwrapped for copying -- **never re-render it as prose in a chat answer**, wrapping renderers link only its first row, which drops the config id and makes the wizard answer `Failed to load config data`; echo it in a fenced code block instead. See [oauth-workflow](oauth-workflow.md). +- `config oauth-url --project NAME --component-id ID --config-id ID [--redirect-url URL] [--no-open]` -- return the OAuth authorization URL for a component that uses OAuth authentication. **Requires a master Storage API token** (canManageTokens privilege) -- non-master tokens fail with `MISSING_MASTER_TOKEN` exit 3 on a fail-fast pre-flight check before any HTTP write happens. (since 0.99.0) the URL is opened in the default browser automatically in interactive human mode (never under `--json`, when stdout is not a terminal, or where `auth login` would not open a browser either: SSH session, container, WSL without a working `wslview`); `--no-open` prints it only. The `--json` result has `browser_opened: false`: never tell the user the link is open. The URL is also printed unwrapped for copying -- **never re-render it as prose in a chat answer**, wrapping renderers link only its first row, which drops the config id and makes the wizard answer `Failed to load config data`; echo it in a fenced code block instead. See [oauth-workflow](oauth-workflow.md). - `config state-get --project NAME --component-id ID --config-id ID [--row-id ID] [--branch ID]` -- (since 0.84.2, #593) read a configuration's runtime `state`. Without `--row-id` returns the root config's state; with `--row-id` returns that row's state (a missing row id fails loudly, it does not silently return `{}`). For row-based components the root state node is unused -- read the row state instead. See [config-state-workflow](references/config-state-workflow.md). - `config state-set --project NAME --component-id ID --config-id ID [--row-id ID] --state JSON|@file|- [--branch ID] [--dry-run] [--yes]` -- (since 0.84.2, #593) write a configuration's runtime `state` via the dedicated branch-scoped `PUT .../state` endpoint. `--state` must be a JSON object (not an array/scalar) under 4 MB. `--row-id` targets a row's state instead of the root. `--dry-run` previews the current-vs-new diff (same shape as `config update --dry-run`) without writing; a no-op (new state equals current) short-circuits with `changed: false` and no write. Guarded write: prompts for confirmation unless `--yes` or `--json`. This is the fix for `config update --set 'state...'`, which never reached runtime state (see [gotchas](references/gotchas.md)). See [config-state-workflow](references/config-state-workflow.md) for the seed-before-migrate playbook. @@ -179,7 +179,7 @@ Requires a **super-admin** Manage API token (same kind as `org setup`). Same def - `storage tables [--project NAME ...] [--bucket-id ID] [--branch ID] [--include-usage]` -- list tables across all connected projects in parallel (multi-project by default, same as `storage buckets`); repeat `--project` to target a subset; `--bucket-id` is applied independently per project (missing buckets become per-project errors); `--branch` requires exactly one `--project`. `--include-usage` (0.88.0+) adds `used_by` per table: the configurations naming it in their **storage input/output mapping** only -- a table id inside a transformation's SQL is NOT a reference. Costs one extra component listing per project (not per table), which is the slow call in a big project; unreadable components degrade to an empty `used_by` - `storage table-detail --project NAME --table-id ID [--branch ID]` -- table detail with columns, types, primary key, row count (branch-aware). Since 0.88.0 (#621) also returns the raw Storage API `definition`: on BigQuery that carries `timePartitioning` / `rangePartitioning` / `clustering` / `requirePartitionFilter` / `partitions[]`, and it is the only way to verify a repartition landed. Human mode prints the layout and a partition COUNT; `--json` passes `definition` through verbatim. Present on every response (untyped tables too), so `null` means the stack omitted the key, not "untyped". Since 0.88.0 (#624) it also RESOLVES column descriptions written by anyone -- the UI, a component, or kbagent -- into `column_details[].description`, with precedence native definition -> `columnMetadata` `KBC.description` -> legacy flat `KBC.column.*` (an alias table falls back to the source table's `columnMetadata`, matching the MCP server). The response always carries `legacy_column_descriptions`, naming the columns still backed by the pre-0.88.0 convention -- human mode warns and points at `storage describe-migrate`. *(since v0.89.0)* Human mode also renders a `Description` column in the Columns table (only when at least one column has one; long text wraps rather than truncating); on 0.88.0 descriptions were visible in `--json` only. Reading never writes, so it is safe under a read-only token or `--deny-writes`. *(since 0.95.0, #761)* The response also carries `backend_path` (the owning bucket's Storage `backendPath`, verbatim: Snowflake `[database, schema]`, BigQuery `[dataset]`) and `sql_path` (the quoted, directly queryable table path, same convention as `storage bucket-detail`'s per-table `sql_path`; `null` when Storage reports no location or the backend is not Snowflake/BigQuery) - `storage create-bucket --project NAME --stage STAGE --name NAME [--description D] [--backend B] [--branch ID]` -- create bucket (branch-aware). With `--branch ID` on a project lacking the `storage-branches` feature (legacy fake-branch), response carries `legacy_branch_storage: true` and human mode prints a warning -- the runner will create a parallel `out.c--*` bucket at job time. See `storage-types-workflow.md` -- `storage create-table --project NAME --bucket-id ID --name NAME [--column col:TYPE[(length)] ...] [--primary-key COL] [--not-null COL ...] [--default NAME=VALUE ...] [--source-table-id ID] [--source-branch-id N] [--time-partitioning-type DAY|HOUR|MONTH|YEAR] [--time-partitioning-field COL] [--time-partitioning-expiration-ms MS] [--range-partitioning-field COL --range-partitioning-start S --range-partitioning-end E --range-partitioning-interval I] [--clustering-field COL ...] [--branch ID] [--if-not-exists] [--timeout SECONDS]` -- create typed table. Base types `STRING/INTEGER/NUMERIC/FLOAT/BOOLEAN/DATE/TIMESTAMP` plus native backend types with length (`VARCHAR(40)`, `NUMBER(18,2)`, `TIMESTAMP_TZ`, `VARIANT`, etc.) -- type/length validation delegated to the Storage API. `--not-null` marks a column `nullable=false`; `--default NAME=VALUE` sets a DEFAULT expression (booleans must be lowercase `true`/`false`). In a dev branch, the target bucket is auto-materialized if it has not yet been written to there -- response surfaces this via `auto_created_bucket: bool`. On legacy fake-branch projects (no `storage-branches` feature), `legacy_branch_storage: true` flags that the runner will use a separate `out.c--*` bucket at job time. `--if-not-exists` (0.47.0+) turns a duplicate-display-name failure into `action: skipped` when the table really exists at the expected id (safe for parallel workers). Since 0.47.1 the skipped envelope reports the EXISTING table's actual `columns`/`primary_key`/`name`, mirrors the request under `requested_columns`/`requested_primary_key`, and sets `schema_drift: true` when they diverge. **`--source-table-id` (0.66.0+, BigQuery only)** copies an existing table's data into the requested partition/clustering layout instead of building from `--column` (schema derived from source -> `--column`/`--not-null`/`--default` forbidden; the two are mutually exclusive). This is the supported way to repartition a populated BigQuery table -- then promote it with `storage swap-tables`. Partition/clustering flags (`--time-partitioning-*`, `--range-partitioning-*`, `--clustering-field`) also work on a plain `--column` create (BigQuery only); time vs range partitioning are mutually exclusive and range bounds are strings. When any source/partition/clustering flag is used, a one-call backend pre-flight rejects non-BigQuery projects (exit 2) before the create. See `storage-types-workflow.md`. **`--timeout SECONDS` (vNEXT+, default 300)** budgets the async create job; a `--source-table-id` copy moves real data and used to inherit the 60s metadata-job default, so a large table reported `STORAGE_JOB_TIMEOUT` for a copy that was still running and would succeed. A local timeout NEVER cancels the job: the error is `STORAGE_JOB_TIMEOUT` (exit 4, `retryable: false`, `details.job_id`) -- follow it with `storage job-detail --job-id ID --wait`, verify the layout with `table-detail`, and never re-run the create (a second copy) +- `storage create-table --project NAME --bucket-id ID --name NAME [--column col:TYPE[(length)] ...] [--primary-key COL] [--not-null COL ...] [--default NAME=VALUE ...] [--source-table-id ID] [--source-branch-id N] [--time-partitioning-type DAY|HOUR|MONTH|YEAR] [--time-partitioning-field COL] [--time-partitioning-expiration-ms MS] [--range-partitioning-field COL --range-partitioning-start S --range-partitioning-end E --range-partitioning-interval I] [--clustering-field COL ...] [--branch ID] [--if-not-exists] [--timeout SECONDS]` -- create typed table. Base types `STRING/INTEGER/NUMERIC/FLOAT/BOOLEAN/DATE/TIMESTAMP` plus native backend types with length (`VARCHAR(40)`, `NUMBER(18,2)`, `TIMESTAMP_TZ`, `VARIANT`, etc.) -- type/length validation delegated to the Storage API. `--not-null` marks a column `nullable=false`; `--default NAME=VALUE` sets a DEFAULT expression (booleans must be lowercase `true`/`false`). In a dev branch, the target bucket is auto-materialized if it has not yet been written to there -- response surfaces this via `auto_created_bucket: bool`. On legacy fake-branch projects (no `storage-branches` feature), `legacy_branch_storage: true` flags that the runner will use a separate `out.c--*` bucket at job time. `--if-not-exists` (0.47.0+) turns a duplicate-display-name failure into `action: skipped` when the table really exists at the expected id (safe for parallel workers). Since 0.47.1 the skipped envelope reports the EXISTING table's actual `columns`/`primary_key`/`name`, mirrors the request under `requested_columns`/`requested_primary_key`, and sets `schema_drift: true` when they diverge. **`--source-table-id` (0.66.0+, BigQuery only)** copies an existing table's data into the requested partition/clustering layout instead of building from `--column` (schema derived from source -> `--column`/`--not-null`/`--default` forbidden; the two are mutually exclusive). This is the supported way to repartition a populated BigQuery table -- then promote it with `storage swap-tables`. Partition/clustering flags (`--time-partitioning-*`, `--range-partitioning-*`, `--clustering-field`) also work on a plain `--column` create (BigQuery only); time vs range partitioning are mutually exclusive and range bounds are strings. When any source/partition/clustering flag is used, a one-call backend pre-flight rejects non-BigQuery projects (exit 2) before the create. See `storage-types-workflow.md`. **`--timeout SECONDS` (0.99.0+, default 300)** budgets the async create job; a `--source-table-id` copy moves real data and used to inherit the 60s metadata-job default, so a large table reported `STORAGE_JOB_TIMEOUT` for a copy that was still running and would succeed. A local timeout NEVER cancels the job: the error is `STORAGE_JOB_TIMEOUT` (exit 4, `retryable: false`, `details.job_id`) -- follow it with `storage job-detail --job-id ID --wait`, verify the layout with `table-detail`, and never re-run the create (a second copy) - `storage upload-table --project NAME --table-id ID --file PATH [--incremental] [--delimiter D] [--enclosure E] [--no-auto-create] [--wait/--no-wait] [--timeout SECONDS] [--branch ID] [--progress]` -- upload a CSV (or a `.csv.gz`, uploaded as-is) into a table; auto-creates the bucket/table from the header unless `--no-auto-create` (branch-aware). Two phases: cloud upload, then an async Storage import job. *(since 0.98.0, #834)* On AWS stacks a file above 64 MiB is sent as an S3 **multipart** upload (64 MiB parts, auto-scaled to fit 10,000 parts, 4 in parallel, per-part retry; peak memory ~4 x part size) -- the old 5 GiB single-PUT ceiling and whole-file-in-RAM read are gone; Azure/GCP unchanged. Human mode shows an upload progress bar on stderr. *(since 0.98.0)* `--wait` (default) waits `--timeout SECONDS` (default 600) for the import; `--no-wait` still waits for the upload, then returns `file_id`, `job_id`, `job_status` (`imported_rows` null while pending) -- follow it with `storage job-detail --job-id ID --wait`. A wait timeout is `STORAGE_JOB_TIMEOUT` (exit 4, `retryable: false`): the import keeps running server-side, so never re-run the upload -- re-import a failed one from the uploaded file with `storage load-file --file-id`. Big files: see `large-upload-workflow.md`. *(since 0.98.0)* `--progress` always reports progress on stderr -- also with `--json` and without a terminal (a bar on a terminal, otherwise one line every 10 s plus a final line: percent, transferred/total, speed over a trailing 30 s window, elapsed, ETA); stdout is untouched, so `--json ... --progress 2>progress.log` keeps a machine-readable result and a readable log - `storage download-table --project NAME --table-id ID [--output FILE] [--columns COL ...] [--limit N] [--where-column COL --where-value VAL ... [--where-operator eq|neq]] [--changed-since WHEN] [--changed-until WHEN] [--branch ID] [--progress]` -- export table to CSV (branch-aware). `--where-column` + `--where-value` (repeatable, OR within the set) + `--where-operator eq|neq` filter rows server-side; `--changed-since`/`--changed-until` (unix ts or strtotime like `-2 days`) filter by import time -- the credential-only, no-workspace way to pull a filtered/incremental slice (0.62.0+). *(since 0.98.0)* `--progress` as on `upload-table`; counts network (compressed) bytes and starts the clock when the download starts (the export job wait is excluded); a sliced export whose manifest has no sizes shows bytes + speed without percent/ETA - `storage add-column --project NAME --table-id ID --column COL:TYPE[(length)] [--not-null] [--default VALUE] [--branch ID]` -- add a single column to an existing table (0.62.0+). Same `name:TYPE(length)` grammar as `create-table --column`; a bare `name` adds an untyped STRING column. Synchronous endpoint (no job to wait on). `--not-null` needs an empty table or a `--default`. Mirror of `delete-column` @@ -187,7 +187,7 @@ Requires a **super-admin** Manage API token (same kind as `org setup`). Same def - `storage truncate-table --project NAME --table-id ID [--table-id ...] [--dry-run] [--yes] [--branch ID]` -- delete all rows while preserving table schema, primary key, descriptions, sharing edges, and downstream dependents. Batch via repeated `--table-id`. Endpoint is uniformly async-via-job on every branch (returns a queued `tableRowsDelete` job; client polls via `_wait_for_storage_job` before returning). Idempotent (truncating an empty table is a no-op). Use when re-seeding a table without losing the schema contract - `storage delete-column --project NAME --table-id ID --column COL [--column ...] [--force] [--dry-run] [--yes] [--branch ID]` -- delete columns from a table (branch-aware) - `storage delete-bucket --project NAME --bucket-id ID [--bucket-id ...] [--force] [--dry-run] [--yes] [--branch ID]` -- delete buckets (branch-aware) -- `storage swap-tables --project NAME --table-id ID --target-table-id ID --branch ID [--dry-run] [--yes] [--timeout SECONDS]` -- swap two storage tables in any branch, including the default/production branch (POST `/tables/{id}/swap`). Both tables exchange physical positions; aliases are NOT transferred (they keep pointing at the same physical position and therefore expose the OTHER table's data after the swap). Service refuses without a branch (active branch via `branch use` works too). Use to flip a typed rebuild ("data_change_log") into the original name ("data") without touching downstream config references. **`--timeout SECONDS` (vNEXT+, default 300)** budgets the async swap job -- a swap on a large BigQuery table outlasts the old 60s default. A local timeout NEVER cancels the swap: the error is `STORAGE_JOB_TIMEOUT` (exit 4, `retryable: false`, `details.job_id`) -- follow it with `storage job-detail --job-id ID --wait` and never re-run the swap (a repeat after it lands swaps the tables back) +- `storage swap-tables --project NAME --table-id ID --target-table-id ID --branch ID [--dry-run] [--yes] [--timeout SECONDS]` -- swap two storage tables in any branch, including the default/production branch (POST `/tables/{id}/swap`). Both tables exchange physical positions; aliases are NOT transferred (they keep pointing at the same physical position and therefore expose the OTHER table's data after the swap). Service refuses without a branch (active branch via `branch use` works too). Use to flip a typed rebuild ("data_change_log") into the original name ("data") without touching downstream config references. **`--timeout SECONDS` (0.99.0+, default 300)** budgets the async swap job -- a swap on a large BigQuery table outlasts the old 60s default. A local timeout NEVER cancels the swap: the error is `STORAGE_JOB_TIMEOUT` (exit 4, `retryable: false`, `details.job_id`) -- follow it with `storage job-detail --job-id ID --wait` and never re-run the swap (a repeat after it lands swaps the tables back) - `storage clone-table --project NAME --table-id ID --branch ID [--dry-run]` -- pull (clone) a production table into a dev branch (POST `/tables/{id}/pull`, operationName `devBranchTablePull`). On `storage-branches` projects a dev branch reads prod tables transparently until the first write, so an in-branch schema mutation (`swap-tables`, dropping a column) fails with a misleading "bucket not found" until the table is materialized branch-local; `clone-table` does that. One-way (default -> branch). Service refuses without a branch (active branch via `branch use` works too). Permission class `write` - `storage snapshot-create --project NAME --table-id ID [--description D] [--branch ID]` -- create a point-in-time snapshot of a table (data + columns + primary key; async `tableSnapshotCreate` job, client polls to completion). The receipt carries `snapshot_id` -- restores are addressed by it. Permission class `write` - `storage snapshots --project NAME --table-id ID [--limit N] [--branch ID]` -- list a table's snapshots (`id`, `createdTime`, `description`, `creatorToken`). Read command: production endpoint by default @@ -274,7 +274,7 @@ Non-SOX Branches 2.0: merge a dev branch into production with review. Alias `mr` - Errors: `FEATURE_NOT_ENABLED` (exit 5, two wordings: feature missing vs SOX) from any command whose target resolved implicitly; `MR_MERGE_CONFLICT` / `MR_NOT_READY_TO_MERGE` from merge (a truncated conflict list carries `details.api_error_params_truncated`); scoped token 403s on everything but `list`. Every result may carry `warnings[]` ## Workspaces (SQL Debugging) -- `workspace create --project ALIAS [--name NAME] [--ui] [--read-only]` -- create workspace (headless ~1s, `--ui` ~15s). Since v0.47.1: Snowflake headless workspaces return a `private_key` PEM field; `password` is empty. BigQuery workspaces keep the default password credential shape. Since vNEXT (#755): a failed create (headless or `--ui`) trashes the `keboola.sandboxes` config it created (`--json` `error.details.sandbox_config_rolled_back`); when the outcome is unknown (a 5xx, read/write `TIMEOUT` or `RETRY_EXHAUSTED` on the create POST, or any error after the `--ui` job was queued except `QUEUE_JOB_FAILED` / `WORKSPACE_NOT_FOUND`) the config is kept because a workspace may exist (`sandbox_config_kept_reason: outcome_unknown`; check with `workspace list`); the `--ui` job and its password reset run on the branch the config lives in, and a failed password reset after a `--ui` create is a `warnings[]` entry (empty `password`; get one with `workspace password`); a cleanup hint names `config delete --project ALIAS ... --branch ID`. `--ui` still ends in `WORKSPACE_NOT_FOUND` on current SaaS stacks -- the sandboxes component no longer provisions SQL workspaces (see gotchas.md); do not retry it. +- `workspace create --project ALIAS [--name NAME] [--ui] [--read-only]` -- create workspace (headless ~1s, `--ui` ~15s). Since v0.47.1: Snowflake headless workspaces return a `private_key` PEM field; `password` is empty. BigQuery workspaces keep the default password credential shape. Since 0.99.0 (#755): a failed create (headless or `--ui`) trashes the `keboola.sandboxes` config it created (`--json` `error.details.sandbox_config_rolled_back`); when the outcome is unknown (a 5xx, read/write `TIMEOUT` or `RETRY_EXHAUSTED` on the create POST, or any error after the `--ui` job was queued except `QUEUE_JOB_FAILED` / `WORKSPACE_NOT_FOUND`) the config is kept because a workspace may exist (`sandbox_config_kept_reason: outcome_unknown`; check with `workspace list`); the `--ui` job and its password reset run on the branch the config lives in, and a failed password reset after a `--ui` create is a `warnings[]` entry (empty `password`; get one with `workspace password`); a cleanup hint names `config delete --project ALIAS ... --branch ID`. `--ui` still ends in `WORKSPACE_NOT_FOUND` on current SaaS stacks -- the sandboxes component no longer provisions SQL workspaces (see gotchas.md); do not retry it. - `workspace list [--project NAME ...] [--orphaned] [--branch ID] [--qs-compatible]` -- list workspaces. `--project` repeatable; `--orphaned` filters to workspaces whose backing `keboola.sandboxes` config is missing. **Since v0.42.0 (#304)**: each entry carries `login_type`, `read_only`, `qs_compatible`, `database`, `warehouse`. New `Login Type` / `RO` / `QS` columns in human mode. `--qs-compatible` pre-filters to RO + whitelisted-loginType workspaces (the canonical data-app shape). **Updated v0.58.0**: `qs_compatible` is keyed by `(backend, loginType)` -- BigQuery workspaces (loginType `default`) now report `qs_compatible: true` and pass `--qs-compatible`; pre-0.58.0 every BigQuery workspace was wrongly excluded (Snowflake's own legacy `default` stays `false`). `--branch` requires exactly one `--project`; without `--branch`, it uses each alias's active branch (`branch use`), else production. **Since 0.96.1 (#766)** the `Target:` line / `targets` key names that branch; the earlier `Info: Using production branch for read` line was wrong - `workspace detail --project ALIAS --workspace-id ID [--branch ID]` -- show connection details. **Since v0.42.0 (#304)**: response carries `login_type`, `read_only`, `qs_compatible`; human mode adds `Login type:` / `Read-only:` / `Query Service compatible:` rows. **Updated v0.58.0**: BigQuery `default` workspaces now report `qs_compatible: true` (was `false`). `--branch` opt-in mirrors `workspace list` - `workspace delete --project ALIAS --workspace-id ID` -- delete workspace @@ -289,7 +289,7 @@ Lifecycle for `keboola.data-apps`. Combines Storage API (config body, git block, - `data-app list [--project NAME ...] [--branch ID]` -- list data apps across projects (Data Science index merged with Storage names). Since v0.43.9 filters out workspace/sandbox deployments (`componentId=keboola.sandboxes`, `type=snowflake`/`bigquery`) that the Data Science `/apps` collection also returns, so the listing matches the Apps UI. Envelope carries `component_id` per app. - `data-app detail --project NAME --app-id ID [--branch ID]` -- merged view (state, desired, url, configVersion, slug, git block with PAT redacted) - `data-app create --project ALIAS --name NAME --slug SLUG (--git-repo URL | --use-managed-git-repo) [--git-public/--no-git-public] [--git-username USER] [--git-pat-env VAR | --git-pat-file PATH | --git-pat-encrypted KBC::Project...] [--auth password|public] [--size tiny|small|medium|large] [--auto-suspend SECONDS] [--type python-js|python|streamlit|r|...] [--workspace/--no-workspace] [--branch ID] [--no-deploy] [--wait] [--timeout SECONDS] [--keep-on-failure] [--dry-run] [--copy] [--reveal]` -- POST shell + encrypt PAT + PUT Storage config (with auto-injected `parameters.id`) + PATCH deploy with the §9 trio. Cleanup-in-finally on failure unless `--keep-on-failure`. Default `--auth password` makes the platform generate a 20-char hex simpleAuth password during the deploy. With `--wait` the password is delivered like `data-app password` once the app runs (since 0.96.1); `--copy` / `--reveal` need `--wait` and a deploy (exit 2 with `--no-deploy` or `--use-managed-git-repo`). `--dry-run` accepts and refuses the same flags and adds `password_delivery` (`prompt` / `clipboard` / `stdout`) plus the warning the real run would give. **Exactly one git source required.** `--use-managed-git-repo` provisions an EMPTY Keboola-hosted repo (POST `useManagedGitRepo:true`), writes NO `parameters.dataApp.git` block, and forces `--no-deploy` (empty repo, nothing to run); mutually exclusive with `--git-repo` and all `--git-*`/PAT flags. Managed-repo deploy WORKS with no credential wiring (verified live -- tic-tac-toe deployed and serving from a Keboola-managed repo). Full flow to a RUNNING app: `git-credentials-create --type http_token --permissions readWrite` -> `git push` your code to the managed repo URL (`data-app git-repo` shows it) -> `data-app deploy`. The platform injects the clone credentials at deploy time, so nothing extra goes into the config; the created credential is only used to authenticate YOUR push. **`--workspace` (since 0.87.0) is ON by default** and writes `runtime.workspace.enabled: true` -- the single switch that makes the platform provision the app's ephemeral workspace and inject `WORKSPACE_ID` / `QUERY_SERVICE_URL` / `KBC_WORKSPACE_MANIFEST_PATH`. Every app that reads Storage needs it; before 0.87.0 kbagent never wrote it, so such an app deployed, reported `state=running`, passed its health probe and read nothing, with NO platform-side diagnostic (check `config detail` -> `configuration.runtime`; a `Missing env vars: WORKSPACE_ID` log line, if any, comes from the app's own code, so its absence rules nothing out). Pass `--no-workspace` only for an app that never touches Storage -- it omits the key entirely (no `enabled: false`), leaving the body identical to 0.86.0. The block is a sibling of `runtime.backend`. Not gated on any project feature. Retrofit an existing app with `config update --merge --set 'runtime.workspace.enabled=true'` then `data-app deploy`. -- `data-app deploy --project NAME --app-id ID [--config-version N] [--wait] [--timeout SECONDS] [--branch ID] [--git-branch BRANCH] [--copy] [--reveal]` -- the §9 redeploy contract. With `--wait` on a password app (since 0.96.1) the password is delivered like `data-app password`: the `c` prompt in a terminal (the command then ends after Enter or 120 s), `--copy`, or `--reveal`; both flags need `--wait` (exit 2 otherwise, before any API call). With no flag and no prompt (no terminal, a background job, `--json`) the password is not read and the output is as before. With a flag, `--json` adds only `ui_url`, `password_delivered_to` and (`--reveal`) `password`; a non-password app with a flag, or any failure to read the password after the deploy, is a `warnings[]` entry with exit 0. The `kbagent serve` create / deploy routes do not deliver the password. Default reads latest Storage version; `--config-version` pins an older version (rollback). Since 0.65.0 the platform injects clone credentials at deploy time, so no manual wiring is needed. Since vNEXT (CLI-15): for a PURE managed repo with no git block yet, deploy first resolves the managed repo's URL and backfills `parameters.dataApp.git`, then pins the resulting Storage `configVersion` -- omitting configVersion alone does NOT provision the app's workspace (`WORKSPACE_ID`/`KBC_WORKSPACE_MANIFEST_PATH`); grant provisioning is gated on that block's presence, not on `configVersion`. A git block already present (external repos, or a managed repo already backfilled) pins the LATEST Storage `configVersion` directly. The result then has `git_backfilled: true`, `git_branch` and a `warnings[]` entry. The branch written is `--git-branch` (since vNEXT); it is used only for this backfill and has no effect on any other deploy. Without `--git-branch`, deploy writes `main` for an app with no runs. For an app that has runs (it deployed before from the managed repo's own default branch, which no API returns), deploy fails with `VALIDATION_ERROR` before any write and asks for `--git-branch` with the branch the code is on. Push the managed repo's code to that branch. An explicit `--config-version` always wins and skips the backfill. +- `data-app deploy --project NAME --app-id ID [--config-version N] [--wait] [--timeout SECONDS] [--branch ID] [--git-branch BRANCH] [--copy] [--reveal]` -- the §9 redeploy contract. With `--wait` on a password app (since 0.96.1) the password is delivered like `data-app password`: the `c` prompt in a terminal (the command then ends after Enter or 120 s), `--copy`, or `--reveal`; both flags need `--wait` (exit 2 otherwise, before any API call). With no flag and no prompt (no terminal, a background job, `--json`) the password is not read and the output is as before. With a flag, `--json` adds only `ui_url`, `password_delivered_to` and (`--reveal`) `password`; a non-password app with a flag, or any failure to read the password after the deploy, is a `warnings[]` entry with exit 0. The `kbagent serve` create / deploy routes do not deliver the password. Default reads latest Storage version; `--config-version` pins an older version (rollback). Since 0.65.0 the platform injects clone credentials at deploy time, so no manual wiring is needed. Since 0.99.0 (CLI-15): for a PURE managed repo with no git block yet, deploy first resolves the managed repo's URL and backfills `parameters.dataApp.git`, then pins the resulting Storage `configVersion` -- omitting configVersion alone does NOT provision the app's workspace (`WORKSPACE_ID`/`KBC_WORKSPACE_MANIFEST_PATH`); grant provisioning is gated on that block's presence, not on `configVersion`. A git block already present (external repos, or a managed repo already backfilled) pins the LATEST Storage `configVersion` directly. The result then has `git_backfilled: true`, `git_branch` and a `warnings[]` entry. The branch written is `--git-branch` (since 0.99.0); it is used only for this backfill and has no effect on any other deploy. Without `--git-branch`, deploy writes `main` for an app with no runs. For an app that has runs (it deployed before from the managed repo's own default branch, which no API returns), deploy fails with `VALIDATION_ERROR` before any write and asks for `--git-branch` with the branch the code is on. Push the managed repo's code to that branch. An explicit `--config-version` always wins and skips the backfill. - `data-app start --project NAME --app-id ID [--wait] [--timeout SECONDS]` -- wake an auto-suspended app at the currently-pinned version. Distinct from deploy: does NOT bump configVersion. - `data-app stop --project NAME --app-id ID [--wait] [--timeout SECONDS]` -- stop a running app (URL and Storage config preserved). - `data-app delete --project NAME --app-id ID [--yes]` -- destructive, cascades to Storage config; URL retired permanently. @@ -364,9 +364,9 @@ Requires the project to be added with its **master ('owner') Storage API token** ## Sync (GitOps) - `sync init --project ALIAS [--directory DIR] [--git-branching] [--adopt-existing] [--with-workspaces]` -- initialize sync working directory; `--adopt-existing` adopts a `.keboola/manifest.json` already written by the kbc Go CLI without overwriting (idempotent; validates `project_id` against the alias token). `--with-workspaces` *(since 0.96.1)* sets `syncWorkspaces` in the manifest, so pull/diff/push/clone also sync shared SQL workspaces (`keboola.sandboxes` without `parameters.id`, `runtime.shared: true`), config only; a `push --force` delete removes the workspace's SQL editor sessions first (a plain push holds it back under `skipped_deletions`). With `--adopt-existing` it turns the key on in an existing manifest. See `sync-workflow.md` > "Shared SQL workspaces". - `sync pull --project ALIAS [--all-projects] [--force] [--theirs] [--dry-run] [--with-samples] [--no-storage] [--no-jobs] [--job-limit N] [--branch ID]` -- download configs to local files. **Auto-inits:** if the target directory has no `.keboola/manifest.json`, pull runs `init` first, so a separate `sync init` is not needed for a first checkout of a project. For large projects (>100 configs), automatically fetches jobs per-config when the grouped API limit is insufficient. `--force` is conflict-aware (since 0.53.0): a locally-modified config whose remote is unchanged is **preserved** (pending delta stays pushable, never silently re-stamped); a true merge conflict (local AND remote both changed since last pull) **aborts** the pull (exit 1, `SYNC_CONFLICT`; `--json` lists `details.conflicts`); local-untouched + remote-changed takes remote. `--theirs` (since v0.72.0) is the supported "discard local, take production" reconcile path: overwrites locally-modified configs/rows, restores deleted/missing files, resolves conflicts by taking remote (no abort, no manifest surgery). Since v0.72.0 plain pull also re-materializes a tracked config whose local dir was deleted (manifest<->disk invariant), so delete-dir-then-pull refetches. Config-level `isDisabled` is kept through pull and push (since v0.72.0) as sparse `is_disabled: true` in `_config.yml` -- absent key = enabled. `--branch` (0.47.0+) per-invocation dev-branch override, beats every other branch source. Ignored components (since 0.91.0): `keboola.sandboxes` + `keboola.mcp-server-tool` are always excluded (`keboola.sandboxes` shared SQL workspaces are synced when the manifest sets `syncWorkspaces`, *since 0.96.1*), unioned with the manifest's `ignoredComponents` list; a component newly ignored has its manifest entry dropped and local directory removed, reported with pull action `"ignored"` (distinct from `"removed"` = genuinely deleted on remote). Config-folder round-trip *(since 0.94.0)*: pull captures each config's UI folder (`KBC.configuration.folderName`, from the branch-only `search/component-configurations` endpoint) into the manifest, and reports `folder_lookup_failed` when that lookup fails, keeping the previously captured folder. **Since 0.98.0 (#745)** `--all-projects` exits **1** when `summary.failed > 0` (a project whose pull raised); `--dry-run` exits **1** on a failure too. -- `sync push --project ALIAS [--all-projects] [--dry-run] [--force] [--allow-plaintext-on-encrypt-failure] [--branch ID] [--no-name-drift-warnings]` -- push local changes (auto-encrypts secrets, fails if encryption fails). Workspace delete *(since 0.96.1)*: in a `syncWorkspaces` tree, `push --force` deleting a shared SQL workspace also deletes its SQL editor sessions (every user's, push branch) and their backend workspaces, which `config restore` does not bring back, so check `push --dry-run --force` (`warnings[]` `workspace_sessions`) first; `--force` is destructive-class (`sync.push --force`, blocked by `--deny-destructive`). Fresh-CREATE writeback updates placeholder manifest entries in place (since 0.47.0) and propagates any `KBC.configuration.*` metadata via `set_config_metadata`. Fresh-CREATE variable binding (since 0.47.2): when a `keboola.variables` config + its values row are created alongside a transformation in the same push, the transformation's `variables_id` / `variables_values_id` placeholders are rebound to the assigned ULIDs and the row's `values` are hoisted even without a `_keboola` block, so `job run` succeeds with no post-push `config variables-set` step (unresolvable/ambiguous links add a `variable_link` entry in `errors[]`, never a broken link). Since 0.96.1 push also remaps shared-code links, flow and orchestrator task `configId`s / `configRowIds` and schedule targets to the configs created in the same push. A link it cannot set is an `errors[]` entry with `change_type` `shared_code_link`, `flow_task_link` or `schedule_target_link` (error code `LINK_UNRESOLVED` for a row id without a new row, `API_ERROR` for a failed PUT). After a failed PUT the local files already hold the new ids and the manifest hash stays stale, so the next `sync push` sends them; this also applies to `variable_link`. The result carries `link_remaps` (`flow_tasks`, `orchestrator_tasks`, `schedule_targets`, `shared_code`, `config_row_ids`); `flow_task_remaps` counts flow tasks only. Never-fetched guard (since v0.72.0): a manifest entry with an empty `pull_hash` and no local files (pre-0.72 name-collision phantom) is **never** planned as a remote DELETE -- diff/push exclude it and report it under `never_fetched` with a warning (run `sync pull` to materialize); local deletion of a properly-pulled config deletes on `push --force`; since 0.96.1 (#792) a plain push deletes nothing and lists the deletion under `skipped_deletions` (also in `--dry-run`), and a config or row deleted on the remote since the last pull is `remote_deleted`, which push never re-creates. Adopted-by-id writeback (since v0.72.0): pushing an untracked file whose `_keboola.config_id` resolves on the branch also writes the manifest entry, so follow-up diffs are stable. Since vNEXT (#792) such a file without a baseline is a `conflict` when it differs from the remote, so push skips it. Since vNEXT (#792) push encrypts every secret before its first write: an `ENCRYPTION_FAILED` stops it before anything reaches the remote (message ends with `Nothing was pushed.`), so the retry creates nothing twice. `--branch` (0.47.0+) per-invocation override; when no `/` subtree exists on disk (since 0.47.2) the local default tree (`main/`) is promoted to the target branch (API writes still target the branch id); `--no-name-drift-warnings` (0.47.0+) drops the cosmetic warnings array. Branch-scoped since v0.89.0 (issue #649): push consumes the diff's changeset, so configs tracked on another branch's tree are never planned as creates -- they ride along on the result envelope under `orphaned` instead (see `sync diff`). **Since 0.91.0 (#686)** the manifest baseline `pull_config_hash` is stamped from the API response (or a read-back), not from the files on disk, so a pushed multi-statement SQL transformation -- or anything disabled in the UI whose local YAML lacks `is_disabled` -- no longer shows permanent phantom `REMOTE MODIFIED` drift; if the config cannot be read back after the write the baseline is left UNTOUCHED and a `warnings[]` entry says to run `sync pull` (never a disk-derived fallback). One legacy change is refused per-change with `SYNC_LEGACY_BOUNDARY`: a tree pulled before statement-boundary markers existed whose only difference from the remote is the lost boundaries (pushing it would collapse separate SQL statements into one) -- run `sync pull` for that project first. Ignored components (since 0.91.0) are filtered out on both sides of the diff push builds on, so a stale local directory for an ignored component (e.g. `keboola.mcp-server-tool`) is never classified as `DELETED` and can never be pushed as a remote deletion. **Since 0.98.0 (#745)** a push with a non-empty `errors[]` exits **1** and the human headline reads `Failed: Pushed: N created, ..., M failed` instead of a green `Success:`. `--all-projects` exits 1 when `summary.failed > 0`, and `summary.failed` now also counts a project whose push returned a non-empty `errors[]` (before, it counted as `success` and its line read `OK`; now the line is marked `x` and states the failed count). A `--dry-run` exits 1 on a failure too. `--json` output is still emitted before the non-zero exit. +- `sync push --project ALIAS [--all-projects] [--dry-run] [--force] [--allow-plaintext-on-encrypt-failure] [--branch ID] [--no-name-drift-warnings]` -- push local changes (auto-encrypts secrets, fails if encryption fails). Workspace delete *(since 0.96.1)*: in a `syncWorkspaces` tree, `push --force` deleting a shared SQL workspace also deletes its SQL editor sessions (every user's, push branch) and their backend workspaces, which `config restore` does not bring back, so check `push --dry-run --force` (`warnings[]` `workspace_sessions`) first; `--force` is destructive-class (`sync.push --force`, blocked by `--deny-destructive`). Fresh-CREATE writeback updates placeholder manifest entries in place (since 0.47.0) and propagates any `KBC.configuration.*` metadata via `set_config_metadata`. Fresh-CREATE variable binding (since 0.47.2): when a `keboola.variables` config + its values row are created alongside a transformation in the same push, the transformation's `variables_id` / `variables_values_id` placeholders are rebound to the assigned ULIDs and the row's `values` are hoisted even without a `_keboola` block, so `job run` succeeds with no post-push `config variables-set` step (unresolvable/ambiguous links add a `variable_link` entry in `errors[]`, never a broken link). Since 0.96.1 push also remaps shared-code links, flow and orchestrator task `configId`s / `configRowIds` and schedule targets to the configs created in the same push. A link it cannot set is an `errors[]` entry with `change_type` `shared_code_link`, `flow_task_link` or `schedule_target_link` (error code `LINK_UNRESOLVED` for a row id without a new row, `API_ERROR` for a failed PUT). After a failed PUT the local files already hold the new ids and the manifest hash stays stale, so the next `sync push` sends them; this also applies to `variable_link`. The result carries `link_remaps` (`flow_tasks`, `orchestrator_tasks`, `schedule_targets`, `shared_code`, `config_row_ids`); `flow_task_remaps` counts flow tasks only. Never-fetched guard (since v0.72.0): a manifest entry with an empty `pull_hash` and no local files (pre-0.72 name-collision phantom) is **never** planned as a remote DELETE -- diff/push exclude it and report it under `never_fetched` with a warning (run `sync pull` to materialize); local deletion of a properly-pulled config deletes on `push --force`; since 0.96.1 (#792) a plain push deletes nothing and lists the deletion under `skipped_deletions` (also in `--dry-run`), and a config or row deleted on the remote since the last pull is `remote_deleted`, which push never re-creates. Adopted-by-id writeback (since v0.72.0): pushing an untracked file whose `_keboola.config_id` resolves on the branch also writes the manifest entry, so follow-up diffs are stable. Since 0.99.0 (#792) such a file without a baseline is a `conflict` when it differs from the remote, so push skips it. Since 0.99.0 (#792) push encrypts every secret before its first write: an `ENCRYPTION_FAILED` stops it before anything reaches the remote (message ends with `Nothing was pushed.`), so the retry creates nothing twice. `--branch` (0.47.0+) per-invocation override; when no `/` subtree exists on disk (since 0.47.2) the local default tree (`main/`) is promoted to the target branch (API writes still target the branch id); `--no-name-drift-warnings` (0.47.0+) drops the cosmetic warnings array. Branch-scoped since v0.89.0 (issue #649): push consumes the diff's changeset, so configs tracked on another branch's tree are never planned as creates -- they ride along on the result envelope under `orphaned` instead (see `sync diff`). **Since 0.91.0 (#686)** the manifest baseline `pull_config_hash` is stamped from the API response (or a read-back), not from the files on disk, so a pushed multi-statement SQL transformation -- or anything disabled in the UI whose local YAML lacks `is_disabled` -- no longer shows permanent phantom `REMOTE MODIFIED` drift; if the config cannot be read back after the write the baseline is left UNTOUCHED and a `warnings[]` entry says to run `sync pull` (never a disk-derived fallback). One legacy change is refused per-change with `SYNC_LEGACY_BOUNDARY`: a tree pulled before statement-boundary markers existed whose only difference from the remote is the lost boundaries (pushing it would collapse separate SQL statements into one) -- run `sync pull` for that project first. Ignored components (since 0.91.0) are filtered out on both sides of the diff push builds on, so a stale local directory for an ignored component (e.g. `keboola.mcp-server-tool`) is never classified as `DELETED` and can never be pushed as a remote deletion. **Since 0.98.0 (#745)** a push with a non-empty `errors[]` exits **1** and the human headline reads `Failed: Pushed: N created, ..., M failed` instead of a green `Success:`. `--all-projects` exits 1 when `summary.failed > 0`, and `summary.failed` now also counts a project whose push returned a non-empty `errors[]` (before, it counted as `success` and its line read `OK`; now the line is marked `x` and states the failed count). A `--dry-run` exits 1 on a failure too. `--json` output is still emitted before the non-zero exit. - `sync clone --source DIR --target ALIAS --target-dir DIR [--bucket-map FILE] [--variable-values FILE] [--instance-rename FILE] [--no-create-buckets] [--dry-run] [--branch ID]` -- clone a reference synced project into a **fresh** target project and parameterize it. Copies the reference tree at `--source` into `--target-dir`, applies declarative overrides from JSON/YAML files (`--bucket-map` `{old_bucket_id: new_bucket_id}` rewrites storage input/output table refs; `--variable-values` `{var_name: value}` overrides `keboola.variables` rows; `--instance-rename` `{old_path_prefix: new_path_prefix}` renames config dirs + manifest paths), re-points the manifest at the target project, and pushes. Because the reference's config ids do not exist in the fresh target, every config is CREATEd fresh and **keboola.flow task `configId`s + transformation variable links are remapped reference->ULID** by push Phase C/D (the push result carries `flow_task_remaps`). Since 0.96.1 push also remaps shared-code links (`shared_code_id`, `shared_code_row_ids`, `{{}}` script placeholders), legacy `keboola.orchestrator` task `configId`s, task `configRowIds` and a schedule's `target.configurationId`; the clone result carries `link_remaps` per kind (see `sync push`). **`warnings[]` (since 0.96.1)**: the clone result (also `--dry-run`; human mode prints them) lists `missing_task_target` (a flow or orchestrator task runs a config that is not in the tree), `encrypted_values_copied` (the `KBC::` paths the target cannot decrypt, as `_config.yml` paths: `secret_keys` get a plaintext + `sync push`, `unencryptable_keys` need `kbagent encrypt values`, `oauth_keys` a new authorization), `data_app_not_deployed` (run `data-app deploy`) and `schedule_not_active` (clone never activates a schedule; `flow schedule` does, and the hint is left out when several schedules run one flow), plus the push warnings. Only the run that creates the configs reports them: a re-run returns `warnings: []`, so keep them from the first run. **Idempotent**: re-running with an existing `--target-dir` skips copy/overrides and just pushes, reporting `no_changes` / `created: 0`. Fails fast (`CONFIG_ERROR`) if the target already contains the reference's configs -- clone requires a fresh/empty target. `SyncService.clone_project(...)` returns a typed `CloneResult` for in-process SDK callers. Override files must be flat `{id: scalar}` mappings *(since v0.89.0)* -- a nested mapping, list, or null value is rejected with `CONFIG_ERROR` (exit 5) naming the key and its actual type. `--branch` is optional on a fresh clone *(since v0.93.1)*. It defaults to the target's production branch, resolved from the API the same way `sync init` does. Pass `--branch ` only to target a dev branch. The config folder (`KBC.configuration.folderName`) is recreated in the target *(since 0.94.0)* -- clone re-points its production configs onto the branch push resolves, so the create-path writeback carries the folder for a plain, `--branch`, and git-branching production clone. **Data-app runtime type (since 0.94.0)**: a `keboola.data-apps` config's type (`python-js` / `streamlit`) lives only on the Data Science `/apps` record, so `sync pull` records it in `_keboola.data_app_type` and clone sends it through `create_app`. A config with no recorded type is created as `python-js`, the default, with a `data_app_type_default` warning *(since 0.96.1)*. Re-pull a tree pulled by an older version before cloning a Streamlit app, or it is created as `python-js`. **Storage buckets (since 0.96.1)**: clone copies configs, not storage, so a cloned config's input/output mappings point at buckets a fresh target lacks. Clone reads the `storage/buckets.json` pull export and creates the missing buckets in the target **by default** (`--no-create-buckets` skips it) -- idempotent (an existing bucket is skipped, a per-bucket API failure is collected in `bucket_errors`), and the created id is `--bucket-map`-remapped so it matches the rewritten config refs. Each bucket is created on the backend the export recorded. A linked (shared) bucket is linked to the same source as in the reference, under the same id, and listed in `linked_buckets` with its source (an empty bucket in its place would stay empty). The source project's sharing settings decide whether the target may link it; a refused link lands in `bucket_errors`. A tree pulled by an older version does not record which buckets are linked, so clone creates no bucket from it and records one `bucket_errors` entry -- re-pull the reference, or pass `--no-create-buckets`. Only the buckets are created, never their tables or data (the export has no table data) -- populate tables by bucket sharing / `storage upload-table`, or by running the flows. **Since 0.98.0 (#745)** a clone with a non-empty `errors[]` or `bucket_errors[]` exits **1** and the human headline reads `Failed: ...` with the failed count -- a clone where every config failed used to print `Success ... 0 created` and exit 0. A re-run (`no_changes`) with a `bucket_errors[]` entry exits 1 too. -- `sync diff --project ALIAS [--all-projects] [--branch ID]` -- 3-way diff (local vs base vs remote), detects conflicts. `--branch` (0.47.0+) per-invocation dev-branch override. Branch-scoped since v0.89.0 (issue #649): the local side is read from exactly ONE tree (the target branch's subtree, or `main/` when the target has none). Manifest entries belonging to another branch's tree -- what `sync pull --branch ` leaves behind when it re-targets the manifest -- are excluded from the changeset and reported under `orphaned` (`summary.orphaned` + details with `component_id`, `config_id`, `path`, `branch_id`, `branch_path`, `exists_on_target`, `reason`, `hint`); human mode previews the first 10. An orphaned FILE whose `_keboola.config_id` still resolves on the target is adopted (diffed as `unchanged`/`modified`), never re-created -- since vNEXT (#792) an adopted file without a baseline (`_keboola.base_config_hash`) is `conflict` when it differs, never `modified`; same-tree id claims keep the #482/#497 fork-by-copy CREATE. Fix a non-zero `summary.orphaned` with `sync pull`. **Since 0.91.0 (#686)** a manifest entry without `metadata.config_hash_version` (written by a pre-0.91.0 kbagent) is compared leniently: a stored hash equal to the pre-0.91.0 hash of the SAME remote config counts as in sync, so the phantom `codes changed` entries disappear immediately; every other field is still pinned by that hash, so real remote drift is unaffected. One `sync pull` per project stamps the version and ends the leniency. Ignored components (since 0.91.0) -- `keboola.sandboxes`, `keboola.mcp-server-tool`, and anything listed in the manifest's `ignoredComponents` -- are excluded from BOTH sides of the comparison, so a stale local directory for one of them never shows up as `DELETED`. Since 0.96.1 (#792) a config or row the manifest fetched from the target branch and that is missing on the remote is `remote_deleted` (human: `- REMOTE DELETED`, `summary.remote_deleted`): run `sync pull`, push never re-creates it. `DELETED` changes are applied only by `sync push --force`. **Since 0.98.0 (#745)** `--all-projects` exits **1** when `summary.failed > 0` (a project whose diff failed), like `sync pull --all-projects`. +- `sync diff --project ALIAS [--all-projects] [--branch ID]` -- 3-way diff (local vs base vs remote), detects conflicts. `--branch` (0.47.0+) per-invocation dev-branch override. Branch-scoped since v0.89.0 (issue #649): the local side is read from exactly ONE tree (the target branch's subtree, or `main/` when the target has none). Manifest entries belonging to another branch's tree -- what `sync pull --branch ` leaves behind when it re-targets the manifest -- are excluded from the changeset and reported under `orphaned` (`summary.orphaned` + details with `component_id`, `config_id`, `path`, `branch_id`, `branch_path`, `exists_on_target`, `reason`, `hint`); human mode previews the first 10. An orphaned FILE whose `_keboola.config_id` still resolves on the target is adopted (diffed as `unchanged`/`modified`), never re-created -- since 0.99.0 (#792) an adopted file without a baseline (`_keboola.base_config_hash`) is `conflict` when it differs, never `modified`; same-tree id claims keep the #482/#497 fork-by-copy CREATE. Fix a non-zero `summary.orphaned` with `sync pull`. **Since 0.91.0 (#686)** a manifest entry without `metadata.config_hash_version` (written by a pre-0.91.0 kbagent) is compared leniently: a stored hash equal to the pre-0.91.0 hash of the SAME remote config counts as in sync, so the phantom `codes changed` entries disappear immediately; every other field is still pinned by that hash, so real remote drift is unaffected. One `sync pull` per project stamps the version and ends the leniency. Ignored components (since 0.91.0) -- `keboola.sandboxes`, `keboola.mcp-server-tool`, and anything listed in the manifest's `ignoredComponents` -- are excluded from BOTH sides of the comparison, so a stale local directory for one of them never shows up as `DELETED`. Since 0.96.1 (#792) a config or row the manifest fetched from the target branch and that is missing on the remote is `remote_deleted` (human: `- REMOTE DELETED`, `summary.remote_deleted`): run `sync pull`, push never re-creates it. `DELETED` changes are applied only by `sync push --force`. **Since 0.98.0 (#745)** `--all-projects` exits **1** when `summary.failed > 0` (a project whose diff failed), like `sync pull --all-projects`. - `sync status [--directory DIR]` -- show locally modified/added/deleted configs. Also surfaces `plaintext_secret_warnings` (since 0.55.0): in-sync configs/rows whose `#`-secrets are still plaintext on the remote (a leftover from pre-0.54.0 writes; #378). Pending (un-pushed) edits are not flagged. Fix = re-push on >=0.54.0 + rotate (version history keeps the plaintext). - `sync branch-link --project ALIAS [--branch-id ID] [--branch-name NAME]` -- link git branch to Keboola dev branch - `sync branch-unlink [--directory DIR]` -- remove git-to-Keboola branch mapping @@ -494,7 +494,7 @@ CLI parity for the `/agents` REST surface. Reads/writes `/agents.jso - `doctor` -- health checks (no `--fix` since v0.85.0 -- it only installed the MCP server). Includes a `sync_secrets` check (since 0.55.0): when run inside a sync working tree (`.keboola/manifest.json`), warns if any in-sync config holds plaintext `#`-secrets (#378); `skip` outside a sync tree. Also an `mcp_tool_tasks` check: FAILs when `agents.json` holds tasks using the removed `mcp_tool` action, listing each task in `details.tasks[]` -- map the tool to its native command via `docs/mcp-migration.md` and recreate the task as `--type cli_command` - `version` -- show the kbagent version and check for kbagent updates (kbagent only since v0.85.0); on a standalone binary it advertises the native channel's upgrade command instead of `kbagent update` (v0.79.0+) - `context` -- full usage instructions for AI agents -- `serve [--host HOST] [--port PORT] [--ui] [--ui-dist PATH] [--reload] [--log-level LVL] [--cors-origin ORIGIN] [--config-dir DIR] [--no-banner]` -- launch the FastAPI HTTP server backing the web UI. `--ui` mounts the built SPA at `/` and authenticates the browser with an HttpOnly `kbagent_session` cookie (nothing is injected into `index.html`). `--no-banner` *(since 0.90.0)* suppresses the UI's "What's new" popup -- a curated per-version highlights modal shown once per version, dismissal persisted in localStorage `kbagent.whatsnew.seen`. The SPA reads the switch from `GET /ui-config` -> `{"banner": bool}`; it gates only the UNSOLICITED popup, so the command palette's "What's new" action still opens it on request. `--config-dir` picks the directory the server exposes (projects AND the `permissions` policy its routes enforce -- every route since vNEXT, only `/auth/*` and `/merge-requests/*` on 0.98.0 and older); *(since 0.91.0)* an explicit root-level `kbagent --config-dir Y serve` is honored too, with the serve-level flag winning when both are given -- on 0.90.1 and older the root-level one was silently ignored, so pass it to `serve` itself there. Requires the optional `server` extra +- `serve [--host HOST] [--port PORT] [--ui] [--ui-dist PATH] [--reload] [--log-level LVL] [--cors-origin ORIGIN] [--config-dir DIR] [--no-banner]` -- launch the FastAPI HTTP server backing the web UI. `--ui` mounts the built SPA at `/` and authenticates the browser with an HttpOnly `kbagent_session` cookie (nothing is injected into `index.html`). `--no-banner` *(since 0.90.0)* suppresses the UI's "What's new" popup -- a curated per-version highlights modal shown once per version, dismissal persisted in localStorage `kbagent.whatsnew.seen`. The SPA reads the switch from `GET /ui-config` -> `{"banner": bool}`; it gates only the UNSOLICITED popup, so the command palette's "What's new" action still opens it on request. `--config-dir` picks the directory the server exposes (projects AND the `permissions` policy its routes enforce -- every route since 0.99.0, only `/auth/*` and `/merge-requests/*` on 0.98.0 and older); *(since 0.91.0)* an explicit root-level `kbagent --config-dir Y serve` is honored too, with the serve-level flag winning when both are given -- on 0.90.1 and older the root-level one was silently ignored, so pass it to `serve` itself there. Requires the optional `server` extra ## Global Flags | Flag | Description | diff --git a/plugins/kbagent/skills/kbagent/references/data-app-workflow.md b/plugins/kbagent/skills/kbagent/references/data-app-workflow.md index 55b8cfe4..9fe13387 100644 --- a/plugins/kbagent/skills/kbagent/references/data-app-workflow.md +++ b/plugins/kbagent/skills/kbagent/references/data-app-workflow.md @@ -338,7 +338,7 @@ kbagent --json data-app git-repo --project prod --app-id 12345678 # -> https_u git push "https://@/keboola/app-12345678.git" HEAD:main # 4. Deploy. The platform injects the clone credentials at deploy time, so no -# credential is wired into the config. Since vNEXT (CLI-15), this step also +# credential is wired into the config. Since 0.99.0 (CLI-15), this step also # backfills parameters.dataApp.git from the managed repo's URL before # pinning a version -- required for the app to get a workspace at all. kbagent data-app deploy --project prod --app-id 12345678 --wait @@ -355,14 +355,14 @@ contract), so `data-app deploy` on a pure managed repo deploys straight from `app.managedGitRepoId`. If a deploy ever reverts to stopped, diagnose it with `data-app runs` (`failure_reason` + `startup_logs`). -**Workspace access is a separate concern from cloning (since vNEXT; CLI-15).** +**Workspace access is a separate concern from cloning (since 0.99.0; CLI-15).** Cloning needs no credential wiring, as above -- but an app that also reads Storage (`runtime.workspace.enabled: true`, the `--workspace` default) needs a workspace grant, and that grant is gated on `parameters.dataApp.git` being present in Storage config, independent of `managedGitRepoId`. Step 4 above now backfills that block automatically before the first deploy, so this flow needs no extra manual step. **Push to the backfilled branch:** the block sets -`branch` from `data-app deploy --git-branch` (since vNEXT), because the +`branch` from `data-app deploy --git-branch` (since 0.99.0), because the managed repo's `git-repo` lookup returns no branch to read. Without the flag, deploy writes `main` for an app with no runs. An app that has runs deployed before from the repo's own default branch, so deploy fails with diff --git a/plugins/kbagent/skills/kbagent/references/gotchas.md b/plugins/kbagent/skills/kbagent/references/gotchas.md index 445fde76..1f70b104 100644 --- a/plugins/kbagent/skills/kbagent/references/gotchas.md +++ b/plugins/kbagent/skills/kbagent/references/gotchas.md @@ -322,7 +322,7 @@ Versioning convention: heuristic. `--device-code` forces the device flow unconditionally. Once the browser callback succeeds (the authorization code was received), there is **no** fallback -- an exchange failure past that point is terminal. - Before the browser wait starts, the CLI prints a notice (since vNEXT; to + Before the browser wait starts, the CLI prints a notice (since 0.99.0; to stderr with `--json`): it waits up to 115 s, and a browser that shows the project list instead of "Login complete" cannot return. The way out is Ctrl+C and the command the notice names: `kbagent auth login --stack @@ -429,7 +429,7 @@ Versioning convention: `--dry-run`; in `--json` it arrives in an additive top-level `warnings` array, so a consumer must not assume the key is absent. - **`--token VALUE` puts a Storage token in the user's shell history; use - `--token-stdin` instead** (since vNEXT). `project add` and `project edit` + `--token-stdin` instead** (since 0.99.0). `project add` and `project edit` both take four mutually exclusive sources (a second one is exit 2): `--token-stdin` (hidden prompt on a TTY, reads a pipe otherwise), `--token-file PATH`, `--token-env NAME`, and `--token VALUE`. Never hand a @@ -437,13 +437,13 @@ Versioning convention: `--token-stdin`. If you must run it, ask them to write the token to a file and pass `--token-file`. In CI use `--token-env`. `--token VALUE` warns on a terminal that the token is now in the history. -- **`--token-file` deletes the file it read** (since vNEXT). Only after the +- **`--token-file` deletes the file it read** (since 0.99.0). Only after the command succeeds -- never on `--dry-run`, never after a failure, so a bad token does not cost the user the file. `--keep-token-file` turns the delete off for a read-only mount. A delete that fails is a warning, not an error. Note that this unlinks the file; it does not scrub the bytes off the disk. - **`project edit` ignores `KBC_TOKEN`; `project add` reads it** (since - vNEXT). Not an oversight. A new token rewrites the alias's `project_id` and + 0.99.0). Not an oversight. A new token rewrites the alias's `project_id` and `project_name` from whatever it verifies as, and nothing checks that the token belongs to the same project, so an exported `KBC_TOKEN` could silently repoint an alias at a different project during a plain `--new-alias`. Name @@ -1377,7 +1377,7 @@ under an active branch. `--branch` requires exactly one `--project`. ## `workspace create --ui` cannot produce a UI-visible SQL workspace; a failed create no longer leaves a sandbox config behind -Applies since vNEXT (issue #755). The `--ui` path queues a `keboola.sandboxes` +Applies since 0.99.0 (issue #755). The `--ui` path queues a `keboola.sandboxes` job with `task: create`. That component **dropped Snowflake/BigQuery workspace provisioning in March 2026**: the `create` task now only registers a sandbox-service record and writes `parameters.id` back into the config -- no @@ -2223,7 +2223,7 @@ config, the retry fires, and the retry destroys it for good. ## A `STORAGE_JOB_TIMEOUT` is not a failure -- the job is still running -*(since vNEXT, #713)* +*(since 0.99.0, #713)* - `storage create-table` and `storage swap-tables` -- the two halves of the BigQuery repartition path -- both move real data, yet both used to inherit @@ -2231,7 +2231,7 @@ config, the retry fires, and the retry destroys it for good. on an 800 MB BigQuery table: create ~15s, swap ~31s. A table a few times larger blew the budget, so a healthy operation was reported as `STORAGE_JOB_TIMEOUT`. -- **Since vNEXT both default to 300s and accept `--timeout SECONDS`.** The +- **Since 0.99.0 both default to 300s and accept `--timeout SECONDS`.** The REST routes (`POST /storage/tables/{p}`, `POST /storage/tables/{p}/{tid}/swap`) take an optional `timeout` field; a zero, negative, NaN or infinite value is a 422. On the CLI the same value is a usage error: exit 2, @@ -3090,7 +3090,7 @@ type inventory and examples. ## Token handling - Tokens are always masked in output (e.g. `901-...pt0k`) -- this is normal -- Token can be passed via `--token-stdin` (preferred), `--token-file`, `--token-env` (all three since vNEXT), `--token`, `KBC_TOKEN` env var (`project add` only), or interactive prompt (`project add` only) +- Token can be passed via `--token-stdin` (preferred), `--token-file`, `--token-env` (all three since 0.99.0), `--token`, `KBC_TOKEN` env var (`project add` only), or interactive prompt (`project add` only) - Manage API token (since v0.29.0): default-deny on env -- via interactive hidden prompt; pass top-level `--allow-env-manage-token` to opt in to `KBC_MANAGE_API_TOKEN`. Never as CLI argument. See the `(since v0.29.0)` entry at the top of this file. - Master token for sharing: `KBC_MASTER_TOKEN_{ALIAS}` (e.g. `KBC_MASTER_TOKEN_PROD`) or `KBC_MASTER_TOKEN` as global fallback. Alias is uppercased, hyphens become underscores. Required for `sharing share` and `sharing unshare`; `sharing list/link/unlink` use regular project tokens. @@ -4249,7 +4249,7 @@ Other behaviors of this family: ## `data-app` managed-repo deploy: the platform injects clone creds; deploy backfills the git block (since v0.65.0; guidance corrected v0.65.1 -- no credential wiring needed) -*(since vNEXT)* `data-app deploy` no longer omits configVersion for a pure +*(since 0.99.0)* `data-app deploy` no longer omits configVersion for a pure managed repo (CLI-15). It backfills `parameters.dataApp.git` and pins the resulting version. Read the bullets below with that in mind; the last bullet has the details. @@ -4281,13 +4281,13 @@ The end-to-end flow that is **verified to deploy and serve** (tic-tac-toe on The actual bug fixed in 0.65.0 was **kbagent always-pinning `configVersion`**: - **configVersion at deploy depends on the source location.** In 0.65.0 through - the release before vNEXT, `data-app deploy` **omitted** configVersion for a + the release before 0.99.0, `data-app deploy` **omitted** configVersion for a *pure* managed repo (no git block) and pinned the **latest** Storage configVersion only when a git block was present (external repos). Pinning a managed app's no-git-block Storage config made the runtime demand `dataApp.git.repository` and the deploy reverted to `stopped` -- that was the misdiagnosed "could not read Username" symptom, NOT a missing credential. - Omitting configVersion fixed that crash. Since vNEXT, deploy backfills the git + Omitting configVersion fixed that crash. Since 0.99.0, deploy backfills the git block instead (last bullet). There is **no** cross-stack "platform doesn't inject credentials" gap (issue #454 closed as not-a-bug). - **Diagnose a stopped-reverting deploy with `data-app runs`**, not `data-app @@ -4298,7 +4298,7 @@ The actual bug fixed in 0.65.0 was **kbagent always-pinning `configVersion`**: command on the misdiagnosis that a credential had to be wired into the config. It was removed in 0.65.1 -- it is unnecessary because no platform-injection gap exists. -- **Correction (since vNEXT, CLI-15): omitting configVersion alone does NOT +- **Correction (since 0.99.0, CLI-15): omitting configVersion alone does NOT provision a workspace.** The 0.65.0 fix above solves the "could not read Username" crash, but a *pure* managed repo (no git block yet) still deployed with no `WORKSPACE_ID`/`KBC_WORKSPACE_MANIFEST_PATH`, even with @@ -4306,7 +4306,7 @@ The actual bug fixed in 0.65.0 was **kbagent always-pinning `configVersion`**: no-platform-diagnostic failure mode as the `--workspace` gotcha above. Root cause: workspace grant provisioning is gated on `parameters.dataApp.git` being present in Storage config, independent of `managedGitRepoId`/ - `hasManagedGitRepo`/`configVersion`. Fixed since vNEXT: `data-app deploy` now + `hasManagedGitRepo`/`configVersion`. Fixed since 0.99.0: `data-app deploy` now resolves the managed repo's URL and backfills that git block (repository URL, branch from `--git-branch`) *before* pinning a version, for any pure managed repo -- no manual `config update --merge` step needed. No API returns the @@ -5443,7 +5443,7 @@ since 0.91.0 -- an explicit root-level `kbagent --config-dir`, then `KBAGENT_CONFIG_DIR`, then the local/global chain), and `kbagent serve` forwards only the session FLAGS of the invocation on top. The three `/auth/*` routes (`server/routers/auth.py`) and the `/merge-requests/*` routes declare -`Depends(require_permission(...))` themselves; since vNEXT every other route is +`Depends(require_permission(...))` themselves; since 0.99.0 every other route is checked through one central table (below) -- see `docs/web-server.md` for the endpoint shapes. @@ -5452,7 +5452,7 @@ endpoint shapes. `--deny-writes` appends `cli:write`, which spans write+destructive+admin, so the CLI callback blocks the `serve` command itself: `Error: Operation 'serve' is blocked by the active permission policy.`, exit code 6, no - uvicorn. `--deny-destructive` does start the server, and since vNEXT it + uvicorn. `--deny-destructive` does start the server, and since 0.99.0 it blocks every destructive route (no `/auth/*` operation is destructive). - **The reachable recipe is a persisted policy in the SERVED config dir.** Verified live: @@ -5469,7 +5469,7 @@ endpoint shapes. A `--mode deny` policy works too, but then `serve` (and the reads you want to keep) must be in its allow list, or the server will not start either. -- **A deny policy firewalls the WHOLE REST surface (since vNEXT, #655).** +- **A deny policy firewalls the WHOLE REST surface (since 0.99.0, #655).** Every route is classified in `server/route_permissions.py` and checked by one app-level dependency against the served config dir's policy, so `permissions set --mode deny --deny cli:write` now blocks `POST @@ -5486,7 +5486,7 @@ endpoint shapes. and `models` / `items/{kind}` / `import` / `promote` asking for (or inheriting from the target model) `organization` scope, plus `build` into an organization-scope model, answer 403 under `--deny-destructive`. -- **`GET /permissions/show` (since vNEXT)** reports the EFFECTIVE policy -- +- **`GET /permissions/show` (since 0.99.0)** reports the EFFECTIVE policy -- the persisted block already merged with the `--deny-*` flags the daemon was launched with. Read-only: there is no REST way to change the policy, so an agent cannot widen the firewall that constrains it. Reachable under any @@ -5501,7 +5501,7 @@ endpoint shapes. - Four registry keys back a serve-only surface with no CLI leaf command: `auth.projects` (the terminal equivalent is `auth register-projects`'s interactive picker), `merge-request.by-branch` (the CLI resolves it behind an - omitted `--merge-request-id`) plus `ai.chat` and `workspace.sql-improve` (since vNEXT -- + omitted `--merge-request-id`) plus `ai.chat` and `workspace.sql-improve` (since 0.99.0 -- the dashboard's Local AI tile and the SQL editor's "improve this query" helper; both are `write`, because both spawn a local CLI process on the host exactly like `agent prompt-improve`). All four are exempted from @@ -5874,7 +5874,7 @@ drops manifest entries whose config is gone from the remote) are closed: ## `sync` and a file that carries a config id but has no manifest entry (#792) -*(since vNEXT, #792)* A `_config.yml` whose `_keboola.config_id` exists on the +*(since 0.99.0, #792)* A `_config.yml` whose `_keboola.config_id` exists on the target branch, but that has no manifest entry, is *adopted*: `sync diff` compares it with that remote config, and `sync push` never creates a duplicate of it. Examples: a `config new --push --output-dir` scaffold, a tree @@ -5909,7 +5909,7 @@ whose manifest was lost or written again, a `main/` file that ## `sync push` encrypts every secret before its first write (#792) -*(since vNEXT, #792)* Before, push encrypted the `#` secrets of each config +*(since 0.99.0, #792)* Before, push encrypted the `#` secrets of each config just before its own write. An `ENCRYPTION_FAILED` on a later config stopped the push after earlier configs were created or updated, and the manifest did not record them. The retry then created those configs again. @@ -6287,7 +6287,7 @@ not, so the wizard authenticates and then 404s on the configuration: first row only. Nothing kbagent prints can prevent that in a renderer it does not control (e.g. an assistant transcript). -Since vNEXT the command therefore opens the URL itself (interactive human mode +Since 0.99.0 the command therefore opens the URL itself (interactive human mode only; `--no-open` opts out) and prints it unwrapped as a copy fallback. It skips the open where `auth login` skips it too: an SSH session (`webbrowser` would start a console browser such as lynx or w3m on the terminal), a @@ -6299,7 +6299,7 @@ of the logins the flow needs. ## An install under the old package name `keboola-agent-cli` does not update to `keboola-cli` -*(since vNEXT, #771)* +*(since 0.99.0, #771)* The package was named `keboola-agent-cli` up to 0.62.0 and is named `keboola-cli` from 0.63.0. Both install the `kbagent` executable, so uv treats an update to `keboola-cli` as a new tool. If a user reports a kbagent that never updates, run `uv tool list` and look for a `keboola-agent-cli` entry. diff --git a/plugins/kbagent/skills/kbagent/references/oauth-workflow.md b/plugins/kbagent/skills/kbagent/references/oauth-workflow.md index 1c4bf114..b4b0881c 100644 --- a/plugins/kbagent/skills/kbagent/references/oauth-workflow.md +++ b/plugins/kbagent/skills/kbagent/references/oauth-workflow.md @@ -6,7 +6,7 @@ Getting an OAuth-based component (`keboola.ex-facebook-ads-v2`, `keboola.ex-goog 1. `kbagent config oauth-url --project NAME --component-id ID --config-id ID` Needs a **master** token (`canManageTokens`); a non-master token fails fast with `MISSING_MASTER_TOKEN` (exit 3). The created token lives 1 hour, so generate the link when the user is ready to click through, not ahead of time. - Since vNEXT the command opens the URL in the user's default browser itself (interactive human mode only -- never under `--json`, never when stdout is not a terminal, never over SSH, in a container or in WSL without a working `wslview`, suppressed by `--no-open`). Under `--json` the result has `browser_opened: false`. + Since 0.99.0 the command opens the URL in the user's default browser itself (interactive human mode only -- never under `--json`, never when stdout is not a terminal, never over SSH, in a container or in WSL without a working `wslview`, suppressed by `--no-open`). Under `--json` the result has `browser_opened: false`. Before you give the link to the user, run `kbagent config detail --project NAME --component-id ID --config-id ID` and note the configuration `version`. Step 3 compares against it. 2. The user completes the provider's consent screen in the browser. 3. Verify: run the same `config detail` again. The authorization is stored when the configuration `version` is higher than the one you noted and `configuration.authorization.oauth_api.id` is set. Do not use `oauth_api.version` as the signal: it is the OAuth Broker API version, the wizard always writes `3`, so it does not change when the user authorizes. Do not claim success without this check; the browser tab tells you nothing. diff --git a/plugins/kbagent/skills/kbagent/references/scaffold-workflow.md b/plugins/kbagent/skills/kbagent/references/scaffold-workflow.md index 16ce488b..9934e36d 100644 --- a/plugins/kbagent/skills/kbagent/references/scaffold-workflow.md +++ b/plugins/kbagent/skills/kbagent/references/scaffold-workflow.md @@ -56,7 +56,7 @@ kbagent --json config new --component-id COMPONENT_ID --project ALIAS --name "Co # (_keboola.config_id) and lands in the subtree of the branch the config was # created in -- the next `sync push` ADOPTS the config (reported as # `modified` until you edit + push) instead of creating a duplicate. -# Since vNEXT (#792) it also records _keboola.base_config_hash, the created +# Since 0.99.0 (#792) it also records _keboola.base_config_hash, the created # config as the API returned it: an edit made in the UI before your push is # reported as `remote_modified` / `conflict` and push does not overwrite it. # On older versions this combo wrote an ID-less scaffold: the next diff --git a/plugins/kbagent/skills/kbagent/references/storage-types-workflow.md b/plugins/kbagent/skills/kbagent/references/storage-types-workflow.md index 8844fd10..3bd9504b 100644 --- a/plugins/kbagent/skills/kbagent/references/storage-types-workflow.md +++ b/plugins/kbagent/skills/kbagent/references/storage-types-workflow.md @@ -363,9 +363,9 @@ Rules: ### Budget both steps on a large table -*(since vNEXT, #713)* +*(since 0.99.0, #713)* -Steps 1 and 2 both move real data. Until vNEXT they inherited the 60s +Steps 1 and 2 both move real data. Until 0.99.0 they inherited the 60s `STORAGE_JOB_MAX_WAIT` meant for metadata jobs -- measured on an 800 MB table, create takes ~15s and the swap ~31s, so a table a few times larger reported `STORAGE_JOB_TIMEOUT` on a job that was still running and would succeed. Both diff --git a/plugins/kbagent/skills/kbagent/references/sync-workflow.md b/plugins/kbagent/skills/kbagent/references/sync-workflow.md index d5732b95..feda5585 100644 --- a/plugins/kbagent/skills/kbagent/references/sync-workflow.md +++ b/plugins/kbagent/skills/kbagent/references/sync-workflow.md @@ -180,7 +180,7 @@ kbagent sync diff --project prod # target: production the file in the target's own tree (`main/`) — so an ordinary production diff after a dev pull reports `unchanged`, not a create. Such a file has no baseline for the target, so a file that differs is a `conflict`, and push - does not send it *(since vNEXT, #792)*; see "Files with a config id and no + does not send it *(since 0.99.0, #792)*; see "Files with a config id and no manifest entry" below. - Configs that exist **only** on the dev branch are excluded entirely. Promote them with `kbagent branch merge`, never by pushing them to production. @@ -419,7 +419,7 @@ Stored in `.keboola/branch-mapping.json`: |------------|---------|--------| | MODIFIED | Local changed, remote unchanged | Safe to push | | REMOTE MODIFIED | Remote changed, local unchanged | Run pull to fetch | -| CONFLICT | Both sides changed, or a file with a config id and no manifest entry differs from the remote *(since vNEXT, #792)* | Resolve manually, then push | +| CONFLICT | Both sides changed, or a file with a config id and no manifest entry differs from the remote *(since 0.99.0, #792)* | Resolve manually, then push | | ADDED | New local config | Push creates it | | DELETED | Local file removed | `push --force` deletes from remote; a plain push lists it under `skipped_deletions` *(since 0.96.1)* | | REMOTE DELETED | Deleted on the remote since the last pull | Run pull; push never re-creates it *(since 0.96.1)* | @@ -434,14 +434,14 @@ Stored in `.keboola/branch-mapping.json`: edit was silently overwritten whenever the remote changed, plain or `--force` - **Pull keeps an untracked file that carries the config id** and has local changes (`skipped`, reason `locally modified, not tracked in the manifest`) - *(since vNEXT, #792)*; only `--theirs` overwrites it. Before, pull + *(since 0.99.0, #792)*; only `--theirs` overwrites it. Before, pull overwrote it with no message. See "Files with a config id and no manifest entry" below - **`--force` is conflict-aware**: see below -- it no longer blindly overwrites - **Push only sends local changes**: remote_modified, conflict and remote_deleted changes are skipped (`skipped` in the result), and local deletions are applied only with `--force` (`skipped_deletions` otherwise) *(since 0.96.1, #792)* -- **Push encrypts every secret before its first write** *(since vNEXT, #792)*: +- **Push encrypts every secret before its first write** *(since 0.99.0, #792)*: an `ENCRYPTION_FAILED` stops the push before anything reaches the remote (the message ends with `Nothing was pushed.`), so running the same push again after the fix is safe. Before, the push stopped after earlier configs @@ -467,7 +467,7 @@ Stored in `.keboola/branch-mapping.json`: ## Files with a config id and no manifest entry -*(since vNEXT, #792)* A `_config.yml` whose `_keboola.config_id` exists on the +*(since 0.99.0, #792)* A `_config.yml` whose `_keboola.config_id` exists on the target branch but has no manifest entry is adopted: diff compares it with that remote config, and push never creates a duplicate. A `config new --push --output-dir` scaffold is such a file, and so is a file in a tree whose diff --git a/plugins/kbagent/skills/kbagent/references/workspace-workflow.md b/plugins/kbagent/skills/kbagent/references/workspace-workflow.md index fb24704a..1f870556 100644 --- a/plugins/kbagent/skills/kbagent/references/workspace-workflow.md +++ b/plugins/kbagent/skills/kbagent/references/workspace-workflow.md @@ -161,7 +161,7 @@ kbagent --json workspace create --project ALIAS --name "shared-debug" --ui ``` **On current SaaS stacks this fails with `WORKSPACE_NOT_FOUND`** (issue #755, -since vNEXT the error explains why and the attempt leaves nothing behind; +since 0.99.0 the error explains why and the attempt leaves nothing behind; when the outcome is unknown, e.g. a timeout or a 5xx, the sandbox config is kept): the `keboola.sandboxes` `create` job no longer provisions Snowflake/BigQuery workspaces -- the UI creates them through SQL Editor sessions, which kbagent diff --git a/pyproject.toml b/pyproject.toml index 6a6acda7..98d40539 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "keboola-cli" -version = "0.98.0" +version = "0.99.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 5edef70b..f320d751 100644 --- a/src/keboola_agent_cli/changelog.py +++ b/src/keboola_agent_cli/changelog.py @@ -26,6 +26,56 @@ # Ordered newest-first. Each value is a list of brief one-line descriptions. CHANGELOG: dict[str, list[str]] = { + "0.99.0": [ + "New (#728): `project add` and `project edit` can take a Storage token without putting " + "it on the command line. Use `--token-stdin` (a hidden prompt in a terminal, or a pipe), " + "`--token-file` (kbagent deletes the file after a successful command, " + "`--keep-token-file` keeps it) or `--token-env NAME`. `--token VALUE` still works, but in " + "a terminal it warns that the token is now in your shell history.", + "Change (#682): `kbagent serve` now applies the permission policy to every REST route, " + "not only to the `/auth` and `/merge-requests` routes. This covers `permissions set`, `--deny-writes` and " + "`--deny-destructive`. Without a policy, nothing changes. The new route " + "`GET /permissions/show` reports the policy that the server uses. Restart " + "`kbagent serve` after `permissions set` or `permissions reset`.", + "Fix (#839): `sync push` no longer overwrites a UI edit of a config that sync adopted by " + "its ID, for example a file from `config new --push --output-dir`. Such a file is now a " + "conflict, and push skips it. `sync push` also encrypts all secrets before its first " + "write. When the encryption fails, push writes nothing, so the next push does not create " + "configs twice.", + "Fix (#720): `storage create-table` and `storage swap-tables` now wait up to 300 seconds " + "for their job and take `--timeout`. Before, a large copy or swap reported a timeout " + "after 60 seconds while the job was still running. After a timeout, kbagent says that " + "the job keeps running and how to check it with `storage job-detail`. Do not run the " + "command again: a second swap swaps the tables back.", + "Fix (#756): `workspace create --ui` now runs its job in the active dev branch. After a " + "failed create, `workspace create` moves the sandbox config to the trash, unless the " + "result is unknown (for example after a timeout). On current stacks the command " + "still cannot create a SQL workspace, because the `keboola.sandboxes` component no " + "longer creates them. The help and the error message now say so.", + "Fix (#765): `data-app deploy` now gives a data app with a Keboola-managed Git repository " + "its workspace on the first deploy. Before, such an app ran without `WORKSPACE_ID` until " + "someone added the Git block to its configuration. kbagent now adds the block itself, " + "for the `main` branch or for the branch that you give with the new `--git-branch` " + "option. An app that already ran needs `--git-branch`.", + "Fix (#726): `config oauth-url` opens the authorization link in your default browser, " + "also on WSL, and prints the link without line breaks. Before, terminals cut the long " + 'link, and the authorization page then failed with "Failed to load config data". Use ' + "`--no-open` to only print the link. The `--json` output has a new key, " + "`browser_opened`.", + "Fix (#841): `auth login` now says what it waits for in the browser. Before, the command " + "looked stuck during the wait. It also says how to switch to the device-code login: " + "Ctrl+C, then `kbagent auth login --device-code`.", + "Fix (#840): when the update at startup fails, kbagent now prints the cause from the " + "installer and the command that recovers the install.", + "Security (#842): hardening from the security audit in #271. The lineage HTML pages load " + "one pinned Mermaid release with an integrity check. The update check writes its version " + "cache in one step, readable only by your user. `sync` warns when it stores a dev branch " + "under a directory with another name. The docs say that the permission policy guards " + "against agent mistakes and is not a sandbox.", + "Note (#843, #844, #832, #831, #829): housekeeping with no user-facing change. A shorter " + "`keboola-expert` agent prompt, and dependency updates in the web UI and its backend " + "(postcss-selector-parser, fast-copy, source-map-js).", + ], "0.98.0": [ "New (#837): `storage upload-table` and `file-upload` now upload large files to AWS " "stacks in parts, so files over 5 GB work and memory stays flat. Files above 64 MiB are " diff --git a/src/keboola_agent_cli/commands/context.py b/src/keboola_agent_cli/commands/context.py index a4c8fac2..9813e617 100644 --- a/src/keboola_agent_cli/commands/context.py +++ b/src/keboola_agent_cli/commands/context.py @@ -547,7 +547,7 @@ directory mirrors the pushed (already encrypted) body exactly like sync pull would materialize it (real transform.sql/.py extracted from the body) instead of placeholder scaffolding. The scaffold also records _keboola.base_config_hash (the - created config as the API returned it; since vNEXT, #792 E), so sync diff compares + created config as the API returned it; since 0.99.0, #792 E), so sync diff compares it 3-way and push never overwrites an edit made in the UI. kbagent config clone --project P --component-id ID --config-id ID --name NAME @@ -616,7 +616,7 @@ kbagent config oauth-url --project NAME --component-id ID --config-id ID [--redirect-url URL] [--no-open] Return the OAuth authorization URL for a component that uses OAuth authentication. Open the URL in a browser to complete the OAuth flow. - Opens it in the default browser in interactive human mode (never with --json); --no-open only prints it (since vNEXT). + Opens it in the default browser in interactive human mode (never with --json); --no-open only prints it (since 0.99.0). kbagent config state-get --project NAME --component-id ID --config-id ID [--row-id ID] [--branch ID] Read a configuration's runtime state (the same dict --with-state attaches @@ -761,7 +761,7 @@ kbagent storage create-table --project NAME --bucket-id BUCKET_ID --name TABLE_NAME [--column col:TYPE[(length)] ...] [--primary-key COL] [--not-null COL ...] [--default NAME=VALUE ...] [--source-table-id ID] [--source-branch-id N] [--time-partitioning-type DAY|HOUR|MONTH|YEAR] [--time-partitioning-field COL] [--time-partitioning-expiration-ms MS] [--range-partitioning-field COL --range-partitioning-start S --range-partitioning-end E --range-partitioning-interval I] [--clustering-field COL ...] [--branch ID] [--if-not-exists] [--timeout SECONDS] Create a typed table. --column repeatable. - - --timeout SECONDS (since vNEXT, default 300): budget for the async create job. A + - --timeout SECONDS (since 0.99.0, default 300): budget for the async create job. A --source-table-id copy moves real data and used to inherit the 60s metadata-job default, so a large BigQuery table reported STORAGE_JOB_TIMEOUT for a copy that was still running and would succeed. A local timeout NEVER cancels the job -- it keeps @@ -865,7 +865,7 @@ branch use'); service guards before any HTTP call when none is set. Any branch works, INCLUDING the default/production branch -- a default-branch swap is how a typed rebuild reaches prod (dev-branch merge does not carry storage schema). - --timeout SECONDS (since vNEXT, default 300): budget for the async swap job. A swap on a large + --timeout SECONDS (since 0.99.0, default 300): budget for the async swap job. A swap on a large BigQuery table outlasts the old 60s metadata-job default. A local timeout NEVER cancels the swap -- it keeps running server-side and usually lands. The error is STORAGE_JOB_TIMEOUT, exit 4, retryable: false, details.job_id; follow it with `storage job-detail --job-id ID --wait` and never @@ -1500,7 +1500,7 @@ git-credentials-create --type http_token --permissions readWrite + push code to the `main` branch of the managed repo URL -> deploy. The platform injects the clone credentials at deploy time, so no credential wiring is - needed. Deploy writes the git block itself (vNEXT+, CLI-15): for a managed + needed. Deploy writes the git block itself (0.99.0+, CLI-15): for a managed app with no parameters.dataApp.git it backfills repository URL + branch into the Storage config, then pins the new version. The branch is deploy --git-branch; without it, `main` for an app with no runs, and a @@ -1756,7 +1756,7 @@ never re-created; a same-tree id claim keeps the #482 fork-by-copy CREATE. An adopted file has no baseline unless it records _keboola.base_config_hash (a config new --push scaffold): without one a difference is a CONFLICT, never modified, and two files with - one id are both conflicts (since vNEXT, #792 E). sync pull keeps such a file with + one id are both conflicts (since 0.99.0, #792 E). sync pull keeps such a file with local changes (skipped, "locally modified, not tracked in the manifest"); --theirs overwrites it. Fix a non-zero summary.orphaned with `sync pull`; promote dev-only configs with `branch merge`, never by pushing them to production. @@ -1768,7 +1768,7 @@ kbagent sync push --project ALIAS [--all-projects] [--dry-run] [--force] [--allow-plaintext-on-encrypt-failure] [--branch ID] [--no-name-drift-warnings] Push local changes. Auto-encrypts secrets. Skips conflicts (pull first). Fails if encryption fails (plaintext secrets never pushed). Use escape hatch flag only if you know what you are doing. - Every secret is encrypted BEFORE the first write (since vNEXT, #792 F): an + Every secret is encrypted BEFORE the first write (since 0.99.0, #792 F): an ENCRYPTION_FAILED stops the push before anything reaches the remote (message ends "Nothing was pushed."), so run the same push again after the fix -- it creates nothing twice. @@ -2346,7 +2346,7 @@ normal chain (KBAGENT_CONFIG_DIR, .kbagent walk-up, global). Passing both is not an error; the serve-level flag wins. That directory decides which projects the REST surface exposes AND which persisted `permissions` - policy its routes enforce (every route since vNEXT; only /auth/* and + policy its routes enforce (every route since 0.99.0; only /auth/* and /merge-requests/* on 0.98.0 and older). NOTE for older installs: up to 0.90.1 `serve` ignored the root-level flag entirely, silently serving a different directory -- there, always pass --config-dir to `serve` itself. diff --git a/src/keboola_agent_cli/permissions.py b/src/keboola_agent_cli/permissions.py index a4404ce8..979b98d7 100644 --- a/src/keboola_agent_cli/permissions.py +++ b/src/keboola_agent_cli/permissions.py @@ -372,7 +372,7 @@ "semantic-layer.scope.set": "write", "semantic-layer.scope.request-create": "write", "semantic-layer.scope.request-delete": "write", - # Serve-only AI helpers (since vNEXT). Neither touches Keboola, but both + # Serve-only AI helpers (since 0.99.0). Neither touches Keboola, but both # spawn a local `claude` / `codex` / `gemini` process on the host, exactly # like `agent prompt-improve` -- classified `write` for the same reason, so # one --deny-writes keeps an agent from spawning subprocesses through any @@ -385,7 +385,7 @@ # DELETE = destructive (it deletes). DELETE is deliberately a rung above # the other mutating verbs -- lumping it in as `write` would let a # `--deny-destructive` session delete through the REST boundary. - # Since vNEXT (issue #655) the serve's own routes DO enforce the served + # Since 0.99.0 (issue #655) the serve's own routes DO enforce the served # config dir's policy on top, so a denied operation is refused at both # ends; before that, only `/auth/*` (since 0.90.1) and `/merge-requests/*` # re-checked the policy and every other router was unguarded, so these diff --git a/src/keboola_agent_cli/server/dependencies.py b/src/keboola_agent_cli/server/dependencies.py index 12d19372..7cba578b 100644 --- a/src/keboola_agent_cli/server/dependencies.py +++ b/src/keboola_agent_cli/server/dependencies.py @@ -255,7 +255,7 @@ def require_permission(operation: str) -> Callable[[PermissionEngine], None]: the registry, so overriding ``get_registry`` in a test cannot disable the check. - Since vNEXT (issue #655) this is the per-route OVERRIDE form, not the + Since 0.99.0 (issue #655) this is the per-route OVERRIDE form, not the default one: every route is classified centrally in ``server/route_permissions.py`` and checked by one app-level dependency. Use this when a route needs an operation the table cannot express -- and diff --git a/src/keboola_agent_cli/services/_data_app_bodies.py b/src/keboola_agent_cli/services/_data_app_bodies.py index ff7dc0ed..65d000f1 100644 --- a/src/keboola_agent_cli/services/_data_app_bodies.py +++ b/src/keboola_agent_cli/services/_data_app_bodies.py @@ -381,7 +381,7 @@ def _backfill_branch(ds_client: Any, app_id: str, git_branch: str | None) -> str An explicit ``--git-branch`` always wins. No API returns the managed repo's default branch (``get_git_repo`` has no branch field). Before - vNEXT a pure managed repo deployed from ``managedGitRepoId`` on the + 0.99.0 a pure managed repo deployed from ``managedGitRepoId`` on the repo's own default branch, so an app with any run history may serve code from a branch other than ``main``. Writing ``main`` for it would repoint it for good. So ``main`` is used only for an app with no runs; otherwise diff --git a/uv.lock b/uv.lock index 052b3eb4..50accbd8 100644 --- a/uv.lock +++ b/uv.lock @@ -465,7 +465,7 @@ wheels = [ [[package]] name = "keboola-cli" -version = "0.98.0" +version = "0.99.0" source = { editable = "." } dependencies = [ { name = "croniter" },