Skip to content

feat(0.27.0): data-app command group -- first-class lifecycle for Keboola data apps - #237

Merged
padak merged 1 commit into
keboola:mainfrom
ottomansky:feat/data-app-lifecycle
May 2, 2026
Merged

feat(0.27.0): data-app command group -- first-class lifecycle for Keboola data apps#237
padak merged 1 commit into
keboola:mainfrom
ottomansky:feat/data-app-lifecycle

Conversation

@ottomansky

@ottomansky ottomansky commented May 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new kbagent data-app command group covering the full lifecycle of Keboola data apps (keboola.data-apps Storage component + Data Science API /apps). Eight subcommands: list, detail, create, deploy, start, stop, delete, password.

The CLI encapsulates four documented footguns so callers cannot hit them:

  1. §9 redeploy contractdata-app deploy always sends the {desiredState=running, configVersion, restartIfRunning=true} trio together. Sending bare desiredState=running (the obvious shape) silently pins to the empty-shell v2 from POST /apps; the runner then errors dataApp.git.repository is required in /data/config.json with no top-level error surfaced -- only visible in the UI's Terminal Logs.
  2. Per-project KMS encryptiondata-app create re-encrypts the plaintext PAT under the target project's KMS via the Encryption API. Refuses to write plaintext if the round-trip does not return a project-scoped (KBC::ProjectSecure*) ciphertext. Ciphertext does not cross projects.
  3. Cleanup-in-finally — if Storage PUT or initial deploy fails after POST /apps, the orphan shell is deleted automatically (override with --keep-on-failure for forensics).
  4. Poll loopstate == stopped is NOT terminal while desiredState == running. The platform transitions created → stopped → starting → running during initial deploy; a naive poll exits prematurely.

Bumps 0.26.00.27.0 (minor — new top-level command group + new API surface).

Architecture

Three-layer split mirrors workspace.py end-to-end:

Layer Path
Client src/keboola_agent_cli/data_science_client.py (new; inherits BaseHttpClient; URL derived as data-science.<stack-suffix>; get_app_password accepts the Manage token per-call so it never lives on the persistent client)
Service src/keboola_agent_cli/services/data_app_service.py (new)
Commands src/keboola_agent_cli/commands/data_app.py (new)

Other surfaces:

  • 8 entries in OPERATION_REGISTRY (read for list / detail / password; write for create / deploy / start / stop; destructive for delete). data-app.password is read for parity with workspace.password.
  • 3 new ErrorCode members: DATA_APP_BUILD_FAILED, DATA_APP_DEPLOY_TIMEOUT, DATA_APP_INVALID_GIT.
  • New data_science client_type plumbed through the hint renderer; --hint client data-app deploy ... generates DataScienceClient instantiation + the §9 trio inline.
  • Service-layer defence-in-depth: length caps + control-char checks on name, description, git_repo, git_branch, git_username so external callers using the service directly cannot smuggle giant payloads or NULs into the audit log.

Live-validation receipts

Replayed the recipe end-to-end against https://connection.us-east4.gcp.keboola.com (project 5726, admin):

Scenario App ID Receipt
data-app list --project cuesta-training-admin -- found pre-existing [APP01] (id 43661270, configVersion=3); errors: []
data-app detail --app-id 43661270 43661270 merged Data Science + Storage view; encrypted PAT redacted as <encrypted>; config_version_storage=3 == config_version_deployed=3
data-app create --git-public --auth public --no-deploy 43661325 new public-repo app; configVersion advanced 1 → 2 (auto-injected parameters.id) → 3 (full PUT)
data-app deploy --app-id 43661325 43661325 URL minted https://kbagent-livetest-pub-43661325.hub.us-east4.gcp.keboola.com; desiredState=running, configVersion=3, restartIfRunning=true (the §9 trio)
data-app stop --app-id 43661325 43661325 state=stopped, desiredState=stopped
data-app delete --app-id 43661325 --yes 43661325 cascade delete of deployment AND Storage config; URL retired

Final cleanup verified: data-app list shows only the original [APP01]. Zero leaked apps, zero leaked Storage configs.

Tests

  • 39 service-level tests in tests/test_data_app_service.py (validation, dry-run, happy-path orchestration, cleanup-in-finally, encryption-failure-aborts-loud, poll-loop semantics including the transient-stopped invariant, length / control-char bounds, redaction helpers).
  • 10 CLI tests in tests/test_data_app_cli.py (mutual-exclusion validation, dual JSON+human output, --yes for delete, manage-token forwarding without leaking the token to stdout/stderr).
  • 2 E2E tests in tests/test_e2e.py::TestE2EDataAppLifecycle (public-repo lifecycle, private-repo + redeploy contract). Gated on E2E_DATA_APP_GIT_REPO_PUBLIC / E2E_DATA_APP_GIT_REPO_PRIVATE env vars.
  • make check: 2,411 passed, 5 skipped. Lint, format, skill-gen, version-sync, changelog-check, error-code lint all clean.

Plugin synchronization map walk

All silent-drift surfaces from CONTRIBUTING.md:251-272 updated:

  • pyproject.toml 0.26.0 → 0.27.0
  • src/keboola_agent_cli/changelog.py — new 0.27.0 entry
  • src/keboola_agent_cli/commands/context.pyAGENT_CONTEXT block added
  • CLAUDE.md ## All CLI Commands
  • plugins/kbagent/.claude-plugin/plugin.json — auto-synced via make version-sync
  • .claude-plugin/marketplace.json — auto-synced
  • plugins/kbagent/agents/keboola-expert.md — 5 matrix rows, version-gate bump, inline gotcha for the §9 contract + cross-project KMS
  • plugins/kbagent/skills/kbagent/SKILL.md — description triggers, workflow link, auto-table regen
  • plugins/kbagent/skills/kbagent/references/commands-reference.md
  • plugins/kbagent/skills/kbagent/references/gotchas.md — two (since v0.27.0) entries (redeploy contract, cross-project KMS)
  • plugins/kbagent/skills/kbagent/references/data-app-workflow.md (new)

Self-review

Two independent reviewer iterations (general-purpose agents with full diff + security checklist) walked the playbook. Iteration 1: 0 BLOCKING / 2 NON-BLOCKING / 5 NIT in CODE; 0 BLOCKING / 1 NON-BLOCKING / 3 NIT in SECURITY. All material findings fixed:

  • get_app_password refactored to inherit retry/backoff via _do_request with per-call headers=
  • Service-layer length + control-char validation added for name / description / git_repo / git_branch / git_username
  • --git-pat-encrypted prefix tightened from any KBC::Project* to the known list (KBC::ProjectSecure::, KBC::ProjectSecureGKMS::, KBC::ProjectSecureKMS::)
  • New _redact_storage_config helper redacts encrypted PATs in raw.storage_config echoes too

Iteration 2 verdict: CONVERGED — zero material findings in both code and security categories.

Test plan

  • make check green locally (2,432 passed; 16 new parametrized hint compile-checks added in iter-5)
  • Live validation against connection.us-east4.gcp.keboola.com (project 5726): all 6 lifecycle scenarios verified, zero leaked artifacts
  • Pytest E2E TestE2EDataAppLifecycle ran against the live admin project: test_data_app_lifecycle_public (id 43661328, configVersion=3, cleanup verified) and test_data_app_lifecycle_private_and_redeploy (real KMS encryption of git PAT, §9 deploy contract executed, id 43661331, cleanup verified)
  • CI green on this PR (multiple iterations; latest commit ad355a8, 1m17s)
  • Manual review of merged sync-map (CLAUDE.md, AGENT_CONTEXT, keboola-expert.md, SKILL.md description triggers) by a human reviewer

What this PR deliberately does NOT cover

  • Reading build / runtime logs — the Data Science API does not expose Terminal Logs as JSON; UI-only. Out of scope.
  • Updating size / auto-suspend / git settings — those live on the Storage config; use kbagent config update --component-id keboola.data-apps --config-id ID --set 'runtime.backend.size="medium"' --merge then data-app deploy. PATCH /apps {config:{...}} is silently dropped by the API.
  • Rotating the simpleAuth password — not supported by the API; delete and recreate.
  • Sync-pull/push of app/{component_id}/{config_name} — manifest stub already exists; full sync support is a follow-up.

@ottomansky
ottomansky force-pushed the feat/data-app-lifecycle branch from e257b73 to 738eff5 Compare May 1, 2026 19:44
@ottomansky

Copy link
Copy Markdown
Contributor Author

Iteration 3 (post-PR review loop) -- amended. Two independent reviewers walked the playbook with fresh context:

Code review: 0 BLOCKING / 6 NON-BLOCKING / 5 NIT.
Security review: 0 BLOCKING / 4 NON-BLOCKING / NITs.

Material non-blockers fixed in the amended commit:

  • Wired DATA_APP_INVALID_GIT to two real validation paths so it is no longer dead-code: (a) reject --git-repo URLs whose scheme is not in the allowlist (https://, http://, ssh://, git://); blocks file://, gopher://, bare git@host:path ssh syntax; and (b) reject --git-pat-env / --git-pat-file values that already start with KBC:: (someone pasting a ciphertext into the wrong flag would otherwise pass through EncryptService's KBC:: short-circuit and reach Storage as a stale ciphertext from another project).
  • Dropped unused auto_restart_enabled parameter on DataScienceClient.patch_app (no caller, no test, dead surface).
  • Deferred the second storage_client construction in DataAppService.deploy_data_app to the branch that actually reads the Storage version (callers passing --config-version no longer pay for an unused client open / close).
  • Dropped the redundant per-worker sort in DataAppService.list_data_apps (the global concat-then-sort dominates).
  • Forwarded description, git_username, git_pat_env, git_pat_file, git_pat_encrypted, timeout, and keep_on_failure through data-app create's emit_hint(...) call AND extended the hint definition's args so kbagent --hint service data-app create ... now generates a DataAppService.create_data_app(...) snippet that actually validates for a private repo (previously the snippet would fail at the service-layer validation because the git creds were dropped).
  • Refactored _has_control_chars to take an allow_whitespace flag so the validator loop uses one helper instead of two implementations of the same rule.

3 new service-level tests cover the new validators (test_git_repo_rejects_file_scheme, test_git_repo_rejects_bare_ssh_syntax, test_git_pat_plaintext_starting_with_kbc_rejected). make check is green: 2,414 passed locally.

@ottomansky
ottomansky force-pushed the feat/data-app-lifecycle branch from 738eff5 to f39d9e1 Compare May 1, 2026 19:50
@ottomansky

Copy link
Copy Markdown
Contributor Author

Iteration 4 (post-PR convergence review) -- amended.

A fresh-context reviewer caught one BLOCKING and several non-blockers I missed. Material findings fixed:

  • BLOCKING fix: the data-app create rendered --hint service snippet was unrunnable: the template wrapped git_pat_env in surrounding quotes that _substitute_params then double-wrapped, producing os.environ[""PAT_VAR""] (SyntaxError); and ServiceRenderer didn't emit import os for the substituted reference (NameError if the syntax did parse). Fixed: template uses os.environ[{git_pat_env}] so the auto-quote produces os.environ["PAT_VAR"]; ServiceRenderer.render now scans resolved args for os. references and emits import os upstream of from pathlib import Path so the snippet imports cleanly.
  • Regression test added in tests/test_data_app_cli.py::TestDataAppHintMode that ast.parse()s the rendered snippets for both --hint client data-app deploy and --hint service data-app create --git-pat-env .... Catches any future hint-template / renderer drift at CI.

make check green: 2,416 passed locally (up from 2,414).

@ottomansky
ottomansky force-pushed the feat/data-app-lifecycle branch from f39d9e1 to ad355a8 Compare May 1, 2026 19:57
@ottomansky

Copy link
Copy Markdown
Contributor Author

Iteration 5 (post-CI convergence review) -- amended.

Iteration-5 reviewer caught a sibling defect to the iteration-4 BLOCKING: the data-app password hint's service step had "manage_token": "<KBC_MANAGE_API_TOKEN>" rendering as the unparseable kwarg manage_token=<KBC_MANAGE_API_TOKEN>,. My iteration-4 regression test only covered create and deploy, so it slipped through.

Fixed:

  • data-app password service hint now uses 'os.environ["KBC_MANAGE_API_TOKEN"]' (matches the client-side hint), so the rendered snippet has a valid subscript expression and the service-side import os detection in the renderer picks it up.
  • The create-hint client step's description placeholder is now substituted (was a literal empty string that rendered as description=,); the <minimal shell> placeholder is now wrapped as a parseable string literal.
  • Tightened the os. heuristic in ServiceRenderer from substring match to a known-token allowlist (os.environ, os.path, os.getenv) so a future hint with an unrelated arg containing the substring os. cannot trigger a spurious unused import.
  • Parametrized regression test in tests/test_data_app_cli.py::TestDataAppHintMode now ast.parse()s every data-app hint in BOTH client AND service modes (16 cases), plus two anchor assertions (test_create_service_hint_imports_os_and_quotes_pat_env, test_password_service_hint_uses_os_environ) so the entire bug class is caught at CI.

make check green: 2,432 passed locally (up from 2,416).

@ottomansky

Copy link
Copy Markdown
Contributor Author

Convergence reached — review loop closed.

Six independent review iterations on this branch (2 pre-PR + 4 post-PR), each with a fresh-context reviewer, each given the full diff and the security checklist. Final iteration: zero material findings in either CODE or SECURITY.

Loop summary:

Iter Findings (CODE / SECURITY) Outcome
1 0/2/5 BLOCKING/NB/NIT in code; 0/1/3 in security All material fixed
2 (none) CONVERGED (pre-PR)
3 0/6/5 in code; 0/4 in security All material fixed
4 1 BLOCKING — data-app create --hint service rendered with doubled quotes + missing import os Fixed: hint template + ServiceRenderer + regression test
5 1 BLOCKING — sibling defect: data-app password service hint rendered as unparseable Fixed: hint template fix + parametrized compile-check across all 8 hints in both client+service modes
6 (none) CONVERGED

The two iter-4/5 BLOCKINGs were exactly the kind of bugs review loops are for: real, user-visible defects that all 5 prior reviewers missed because the test suite only covered the happy path. The fix is now defended by a parametrized compile-check that runs ast.parse on every rendered snippet for every data-app subcommand in both --hint client and --hint service modes (16 cases) plus two anchor assertions for the specific bug shapes. The whole class of bug — "hint snippet renders as something Python can't parse" — is caught at CI from now on.

Final state:

  • Mergeable, mergeStateStatus=CLEAN
  • CI green (1m17s, single check workflow)
  • +4,463 / -7 LOC across 24 files
  • 2,432 tests pass locally; 5 skipped (require E2E env vars)
  • All silent-drift surfaces from CONTRIBUTING.md:251-272 walked
  • Live-validated end-to-end against connection.us-east4.gcp.keboola.com (project 5726): six lifecycle scenarios verified, zero leaked artifacts

Ready for human review.

@padak padak left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

Mechanically clean — full CONTRIBUTING.md plugin-sync map walked correctly. The 4-footgun encapsulation is the headline value of this PR and it is implemented, tested, AND documented end-to-end. Ready to merge from a code-review standpoint.

Two extras worth landing before / shortly after merge:

Asks

docs/TUTORIAL.md extension — new §9 "Data apps lifecycle"

The TUTORIAL is the second onboarding doc after the README. §8 already covers "Advanced storage: native column types"; §9 should mirror the same shape for data apps so a first-time user can go from kbagent project add to a running data app without leaving the TUTORIAL:

  • §9.0 — one-paragraph framing of the 4 footguns the CLI encapsulates (§9 redeploy contract, per-project KMS, cleanup-in-finally, transient-stopped poll). Concise: this is the headline of the feature; users skim TUTORIAL for the "why".
  • §9.1 — Public-repo golden path: data-app create --git-public --no-deploydata-app deploy --wait. Explain the auto-injected parameters.id quirk if relevant.
  • §9.2 — Private-repo golden path: --git-pat-env VAR with the per-project-KMS round-trip mention (no plaintext on argv, no cross-project ciphertext).
  • §9.3 — The "deploy-after-config update" gotcha: Storage bumps the version, but the deployment record's configVersion is a pinned pointer. kbagent data-app deploy is the rollout step; data-app start only wakes a parked container.
  • §9.4 — stop (reversible, zero-cost wake) vs delete (cascades to Storage config; URL retired).
  • §9.5 — (optional) Demo GIF in the same style as §8's demo-storage-types.gif.

README.md — small but visible

Neither README nor TUTORIAL is CI-checked, but they are the first impressions for GitHub visitors:

  • "What it does" table — new Data apps row between Workspaces and Sharing. One sentence highlighting the lifecycle + 4 footguns.
  • "All commands" code block — add the line:
    kbagent data-app   list | detail | create | deploy | start | stop | delete | password
    

NIT (defer if you like)

  1. --hint client data-app deploy without --config-version — rendered Python emits config_version=None, which the deploy endpoint rejects with HTTP 422. The service-hint resolves None at runtime correctly; only the client-hint paste-and-run path hits the wall. Suggest emitting <resolved-from-storage-config> placeholder text + a comment in the rendered snippet so a user knows to fill it in.
  2. data_app_service.py is 1243 lines — three logical sections (lifecycle methods / redaction helpers / validation) split into separate files would help future maintainers. Not a 0.27.0 blocker; raise the discussion if the file grows further (e.g. when --reload-on-config or sync-pull/push for data apps lands).
  3. If TUTORIAL §9 lands, mirror the link from docs/use-cases.md for docs-index consistency.

What I verified

  • Full diff against CONTRIBUTING.md plugin synchronization map (every silent-drift surface touched: AGENT_CONTEXT, CLAUDE.md, keboola-expert.md Rule 6 + matrix + inline gotchas, SKILL.md description triggers + workflow link, commands-reference.md, gotchas.md with 3× (since v0.27.0) tags, new data-app-workflow.md).
  • 4-footgun encapsulation:
    • §9 trio sent together at data_app_service.py:506-510 (create) and :617-621 (deploy).
    • KMS round-trip prefix-check at :1001-1009 (3 ciphertext prefixes whitelisted, refuses plaintext).
    • Cleanup-in-finally at :430-558 with --keep-on-failure opt-out + dedicated test.
    • Transient-stopped poll-loop at :1140-1189 with test_stopped_is_not_terminal_during_initial_deploy covering the create→stopped→starting→running sequence.
  • 70 unit tests (39 service + 10 CLI + 16 parametrized hint compile-checks via ast.parse) + 2 E2E gated on E2E_DATA_APP_GIT_REPO_PUBLIC/_PRIVATE with autouse cleanup fixture.
  • Security: Manage token never persists on DataScienceClient (per-call headers= kwarg only); _redact_storage_config covers all echo sites; length + control-char + URL-scheme defence-in-depth at service boundary; --git-pat-encrypted prefix tightened to 3 specific KMS prefixes (KBC::ProjectSecure::, KBC::ProjectSecureGKMS::, KBC::ProjectSecureKMS::).

@ottomansky
ottomansky force-pushed the feat/data-app-lifecycle branch from ad355a8 to 0b96ccd Compare May 2, 2026 08:42
@ottomansky

Copy link
Copy Markdown
Contributor Author

Docs amendment per the maintainer review — TUTORIAL §9 + README rows.

  • New ## 9. Data apps lifecycle in docs/TUTORIAL.md, mirroring §8's shape end-to-end:
    • Opening prose framing the four footguns the CLI encapsulates (the headline of the feature for the TUTORIAL "why" audience)
    • §9.1 — public-repo golden path: kbagent project adddata-app create --git-public --auth public --no-deploydata-app deploy --wait. Notes the auto-injected parameters.id back-pointer that bumps Storage 1 → 2 → 3.
    • §9.2 — private-repo golden path: --git-pat-env GITHUB_PAT_DATAAPP (no argv leak), per-project KMS round-trip, data-app password with the simpleAuth password retrieval and the explicit "cannot rotate; delete + recreate" call-out.
    • §9.3 — the deploy-after-config update gotcha: deployment record's configVersion is a pinned pointer that does NOT auto-advance. data-app deploy is the rollout step; data-app start is the cheap restart for parked auto-suspended containers (does NOT bump configVersion).
    • §9.4 — stop (reversible / zero-cost wake) vs delete (cascades to Storage config; URL retired permanently).
    • Closing pointer to plugins/kbagent/skills/kbagent/references/data-app-workflow.md for the deeper reference (same convention §8 uses for storage-types-workflow.md).
  • New Data apps row in the README "What it does" table between Sharing and Lineage.
  • New kbagent data-app list | detail | create | deploy | start | stop | delete | password line in the README "All commands" code block.

Demo GIF deferred — Padak's review marked it as (optional), and recording it cleanly requires picking a small public Node repo for §9.1; will land as a polish follow-up. Padak's nit on --hint client data-app deploy rendering config_version=None and the data_app_service.py:1243 line count are also deferred per his "defer if you like" framing.

make check is still green: 2,432 passed locally (same count as before — pure docs change). Force-pushed ad355a8 → 0b96ccd. CI re-running.

…oola data apps

Adds a `kbagent data-app` group covering create, list, detail, deploy,
start, stop, delete, and password retrieval for `keboola.data-apps` +
the Data Science API `/apps`. The CLI encapsulates the documented
footguns so callers cannot hit them:

- §9 redeploy contract: `data-app deploy` always sends the
  `{desiredState=running, configVersion, restartIfRunning=true}` trio
  together. Sending bare `desiredState=running` (the obvious shape)
  silently pins to the empty-shell v2 from `POST /apps`; the runner
  then errors `dataApp.git.repository is required in /data/config.json`
  with no top-level error surfaced.
- Per-project KMS encryption: `data-app create` always re-encrypts the
  plaintext PAT via the target project's Encryption API. Refuses to
  write plaintext if the round-trip does not return a `KBC::Project*`
  ciphertext. Ciphertext does not cross projects.
- Cleanup-in-finally: if the Storage PUT or initial deploy fails after
  `POST /apps`, the orphan shell is deleted unless `--keep-on-failure`.
- Poll loop: `state == stopped` is NOT terminal while
  `desiredState == running` -- the platform transitions
  `created -> stopped -> starting -> running` during initial deploy.

Layering follows the project convention (commands -> services ->
clients) and mirrors workspace.py end-to-end:

- Client: new `DataScienceClient` (`data_science_client.py`) inheriting
  `BaseHttpClient`. URL derived as `data-science.<stack-suffix>` from
  the connection URL. `get_app_password` accepts the Manage token
  per-call so it never lives on the persistent client.
- Service: `DataAppService` (`services/data_app_service.py`) accepts
  both a Storage-client factory and a Data-Science-client factory plus
  the existing `EncryptService`. One method per CLI subcommand.
- Commands: `commands/data_app.py` -- thin Typer subcommands; mutual
  exclusion validation for git auth modes; dual JSON / human output;
  confirmation prompt on delete.
- Permissions: 8 entries added to OPERATION_REGISTRY (read for list /
  detail / password; write for create / deploy / start / stop;
  destructive for delete). `data-app.password` is `read` for parity
  with the existing `workspace.password`.
- Errors: 3 new `ErrorCode` members (`DATA_APP_BUILD_FAILED`,
  `DATA_APP_DEPLOY_TIMEOUT`, `DATA_APP_INVALID_GIT`); existing codes
  cover the rest.
- Hints: new `data_science` `client_type` in the renderer. Hint mode
  generates `DataScienceClient` instantiation + the §9 trio inline.

Tests: 30 service-level tests (validation, dry-run, happy path,
cleanup-in-finally, encryption-failure-aborts-loud, poll-loop semantics
including the transient-stopped invariant), 10 CLI tests (mutual
exclusion, dual output, manage-token forwarding without leaking the
token to stdout), 2 E2E tests gated on
`E2E_DATA_APP_GIT_REPO_PUBLIC` / `E2E_DATA_APP_GIT_REPO_PRIVATE`.

Sync map: pyproject 0.27.0; changelog entry; AGENT_CONTEXT block;
CLAUDE.md `## All CLI Commands`; keboola-expert.md matrix (5 rows) +
inline gotcha + version-gate bump; SKILL.md description triggers +
workflow link + auto-table regen; commands-reference; gotchas.md (two
`(since v0.27.0)` entries -- the redeploy contract and the
cross-project KMS); new data-app-workflow.md; plugin.json /
marketplace.json synced via `make version-sync`.

Bump 0.26.0 -> 0.27.0 (minor): new top-level command group + new
underlying API surface (Data Science).
@ottomansky
ottomansky force-pushed the feat/data-app-lifecycle branch from 0b96ccd to bf20800 Compare May 2, 2026 08:53
@ottomansky

Copy link
Copy Markdown
Contributor Author

Post-amend security review caught a BLOCKING issue + cleanup pass — fixed.

Two independent fresh-context reviewers (one CODE, one SECURITY) walked the docs amendment. Findings:

SECURITY BLOCKING (fixed):

  • The # 35482186a082da9e97d9 example output in §9.2's data-app password block was the actual live simpleAuth password of an admin data app (carried over from an empirical writeup; simpleAuth passwords cannot be rotated, so it was still valid). Replaced with an obvious placeholder <20-character hex password, e.g. a1b2c3d4e5f6a7b8c9d0>. The original app should be delete+recreated to mint a new password — that is out-of-band and unrelated to the merge.

SECURITY NON-BLOCKING (fixed):

  • Real Cuesta admin project identifiers (43661269, 01kqj88t0vktxe0vfhk6ps5kzs) appeared in TUTORIAL §9 and the plugin data-app-workflow.md. Not credentials, but they pin public docs to a specific live project and let outsiders enumerate the URL pattern. Replaced everywhere in public docs with obviously-fake values (12345678, 01abcdefghijklmnopqrstuvwxyz). Test fixtures (tests/test_data_app_service.py) keep the realistic-shaped IDs because they are mock API responses, not public docs.

CODE NON-BLOCKING (fixed):

  • §9.4 # Or: just hit the URL. autoRestartEnabled=true means ... claim was unverified — kbagent does not control or surface that flag. Softened to "the platform typically auto-wakes parked containers".
  • §9.4 data-app delete --yes cascade attribution was ambiguous (kbagent only calls ds_client.delete_app; the cascade is platform-side). Clarified: "the Data Science API cascade-deletes ... server-side."

CODE NIT (fixed):

  • README "Data apps" row was the longest cell in the "What it does" table by ~50%. Tightened from "Encapsulates the redeploy contract and per-project KMS encryption of git PATs so callers never have to hand-wire the documented footguns" to "Hides the redeploy contract and per-project KMS encryption of git PATs."
  • §9.1 jq label printed configVersion= (Data Science API mixedCase) while reading .config_version (kbagent snake_case). Aligned to config_version for consistency.

make check is still green: 2,432 passed locally. Force-pushed 0b96ccd → bf20800.

@ottomansky

Copy link
Copy Markdown
Contributor Author

Convergence reached on the docs amendment.

Iteration 2 (fresh-context reviewer over the sanitized diff 0b96ccd..bf20800): zero material findings in either CODE or SECURITY.

Verifications:

  • Zero hits for the leaked password (35482186a082da9e97d9) or the real Cuesta admin IDs (43661269, 01kqj88t0vktxe0vfhk6ps5kzs) in any public-doc surface (docs/, README.md, plugins/).
  • §9.4 autoRestartEnabled softening preserves the user-facing behaviour (URL hits auto-wake parked containers, ~30-60s cold-boot) and now distinguishes data-app start as the "explicit, observable wake".
  • §9.4 cascade-delete attribution correctly credits the Data Science API server-side.
  • README "Data apps" row tonally consistent with neighbours after tightening.
  • §9.2 placeholder password <20-character hex password, e.g. a1b2c3d4e5f6a7b8c9d0> is unambiguously a placeholder.
  • make check: 2,432 passed, 5 skipped — same as the prior commit.

Note: the reviewer also flagged that other unrelated real-looking IDs exist in docs/TUTORIAL.md and README.md (flow 300555360, project 10539, branch 1294xxx, --project-ids 901,9621,10539) but those predate this PR and trace via git blame to earlier commits — out of scope for this docs amendment.

PR ready for human review.

@padak
padak merged commit 10ba4a0 into keboola:main May 2, 2026
1 check passed
@ottomansky
ottomansky deleted the feat/data-app-lifecycle branch May 11, 2026 15:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants