Skip to content

docs(0.28.0): typify-table-workflow.md -- end-to-end procedure for retypifying typeless Storage tables - #251

Merged
padak merged 1 commit into
feat-0.28.0from
docs/typify-workflow
May 6, 2026
Merged

docs(0.28.0): typify-table-workflow.md -- end-to-end procedure for retypifying typeless Storage tables#251
padak merged 1 commit into
feat-0.28.0from
docs/typify-workflow

Conversation

@padak

@padak padak commented May 6, 2026

Copy link
Copy Markdown
Member

Summary

Adds a dedicated workflow reference file at plugins/kbagent/skills/kbagent/references/typify-table-workflow.md. The existing storage-types-workflow.md (PR #247) had a subsection "Promoting a typed rebuild back into the original name" that covered the swap-tables mechanics, but HOW to profile the typeless data, HOW to choose types, and HOW to hand off to the user were missing. Without those, an AI agent asked "convert this typeless table to native types" still has to invent the procedure -- and gets it wrong on profile-then-CTAS edge cases (NULL accumulation from failed casts, VARCHAR bound under-reports, datelike vs timestamp confusion).

The new 8-phase workflow:

  1. Decide-or-skip rubric -- typify is the wrong answer for tiny daily-recreated tables, aliases, and add-a-column flows. Documented so the agent doesn't apply the workflow indiscriminately.
  2. Isolate in a dev branch -- production keeps the typeless original until merge.
  3. Profile in a workspace -- length / cardinality / parse-failure / scale-precision queries with a decision matrix mapping profile signals to Snowflake native types (and notes for BigQuery).
  4. Build the typed sibling -- storage create-table + copy data via in-workspace INSERT (Option A, Snowflake fast path) or SQL transformation (Option B, BigQuery + audit trail), with row-count and NULL-count verification.
  5. Validate downstream in the dev branch -- config search to find consumers + representative transformation run against the typeless source as baseline before swap.
  6. Swap -- dry-run + actual + verify column types swapped.
  7. Smoke test -- re-run the same downstream against the typed source post-swap; row counts must match Phase 4 baseline.
  8. Cleanup -- after merge, the sibling holding the typeless rows can be deleted.
  9. Handoff protocol -- a structured summary template the AI agent gives the user with phase-by-phase receipts, the merge URL from branch merge, and rollback / cleanup commands. This is the deliverable the user reviews before clicking "merge" in the Keboola UI.

Plus a "Failure modes to anticipate" section covering the realistic ways this goes wrong: VARCHAR bound under-reports, silent CAST nulls, dev-branch baseline failures, legacy_branch_storage projects where the dev-branch isolation guarantee does not hold, and aliased downstream consumers.

Files touched

  • NEW plugins/kbagent/skills/kbagent/references/typify-table-workflow.md (the workflow itself)
  • plugins/kbagent/skills/kbagent/SKILL.md -- workflow-references table gains a row pointing to the new file (hand-maintained, NOT auto-generated)
  • src/keboola_agent_cli/changelog.py -- "Plugin docs" bullet appended to the 0.28.0 entry describing what the workflow covers

No code changes. No new commands. No test changes. Pure docs + a changelog mention so the v0.28.0 release notes capture that the typify procedure is now first-class.

Test plan

  • make check green: 2,483 tests, 6 skipped, lint + format + skill-check + version-check + changelog-check + error-code lint all clean.
  • Cross-references verified: every references/*.md mentioned in the new workflow exists at the expected path (storage-types-workflow.md, branch-workflow.md, workspace-workflow.md, gotchas.md).
  • SKILL.md workflow table edit is in the hand-maintained section (line 248, NOT inside <!-- BEGIN AUTO-GENERATED COMMANDS -->) -- the next make skill-gen will not overwrite it.

…s end-to-end

The PR #247 storage-types-workflow.md gained a small subsection on
'Promoting a typed rebuild back into the original name', but that section
only covered the swap-tables mechanics. AI agents asked to retypify a
Storage table also need to know HOW to profile the data, HOW to choose
types, and HOW to hand off to the user -- those were missing.

New 8-phase workflow:
  0. Decide-or-skip rubric (when typify is the wrong answer).
  1. Isolate in a dev branch (so production stays on the typeless original).
  2. Profile the typeless table in a workspace -- length / cardinality /
     parse-failure / scale-precision queries with a decision matrix that
     maps profile signals to Snowflake native types.
  3. Build typed sibling via storage create-table + copy data via
     in-workspace INSERT or SQL transformation, with row-count and
     NULL-count verification.
  4. Validate downstream consumers in the dev branch (config search +
     representative transformation run against the typeless source).
  5. Swap (dry-run + actual + verify column types swapped).
  6. Re-run downstream as smoke test against the typed source.
  7. Cleanup the sibling after merge.
  8. Handoff protocol -- structured summary the AI agent gives the user
     with phase-by-phase receipts, the merge URL, and rollback / cleanup
     commands.

Plus: SKILL.md workflow-references table gains the new entry; changelog
0.28.0 entry adds a 'Plugin docs' bullet describing the workflow.
@padak
padak merged commit e1285d4 into feat-0.28.0 May 6, 2026
@padak
padak deleted the docs/typify-workflow branch May 6, 2026 14:43
padak added a commit that referenced this pull request May 6, 2026
…bles (#249)

* fix(0.28.0): config update auto-normalizes script[] string -> array (#245) (#246)

* fix(0.27.1): config update auto-normalizes parameters.blocks[].codes[].script string -> array (#245)

The Storage API silently accepts a string for
parameters.blocks[].codes[].script while the Keboola runtime validator
requires an array (`Invalid type for path "root.parameters.blocks.0.codes.X.script".
Expected "array", but got "string"`). The broken push lands as 200 OK,
the version increments, the UI looks fine -- the crash happens only at
job-run time, often hours later via the scheduler, with no attribution
back to the offending write. Issue #245 was reported after a programmatic
refactor crashed three production Snowflake transformations.

kbagent config update now closes the gap on the write side. Defense-in-depth
in our own command surface; bypassing kbagent (raw Storage API REST,
keboola-mcp-server's update_sql_transformation / create_sql_transformation
as of v1.59.x) does NOT inherit the normalization -- a separate upstream
fix is recommended for the MCP server.

## Surfaces (all v0.27.1)

- Helper: normalize_blocks_codes_script(component_id, config) ->
  (config, normalizations) in src/keboola_agent_cli/sync/code_extraction.py.
  SQL transformations get statement-level split via the existing
  split_statements() state machine (respects '...' / "..." / $$..$$ /
  -- / # / // / /* ... */); Python / R / kds-team.app-custom-python and
  any other component sharing the parameters.blocks[].codes[].script
  shape get a single-element [script] wrap. Already-array script values
  pass through unchanged.

- Service: ConfigService.update_config calls the helper immediately
  after _resolve_configuration, before client.update_config. Both the
  real-write path and the --dry-run preview path normalize, so what
  the operator sees in dry-run matches what would actually land on
  Storage. The result envelope gains a normalizations: [{path, action:
  "sql_split"|"wrap_array", before_type, after_type, after_length}]
  field per write -- empty list when input was already valid.

- CLI: human mode prints a yellow `Auto-normalized N script field(s)
  to array (string -> list). See --json for details.` warning followed
  by a per-element trace, so the silent fix is observable.

## Bonus silent-gap fix

SQL_TRANSFORMATION_COMPONENTS in code_extraction.py was missing
keboola.google-bigquery-transformation and keboola.duckdb-transformation,
so kbagent sync push previously did NOT split semicolons in BigQuery /
DuckDB transformations -- it joined every statement into one script
element. Same failure shape as #119 (closed for Snowflake / Synapse /
Oracle / Redshift), just on different backends. The new fragment-based
is_sql_transformation_component() helper also matches future variants
(*-bigquery-transformation, *-duckdb-transformation, *-exasol-transformation,
*-teradata-transformation, *-transformation-v2) so newer SQL backends
do not require an edit to the exact set.

## Tests

- 35 new unit tests in tests/test_normalize_script.py: registry detection
  (exact + fragment fallback), splitter edge cases (semicolons inside
  block comments and string literals), per-component dispatch (SQL
  split vs Python wrap vs already-array passthrough), ConfigService
  integration (write path, dry-run path, --set path), CLI surfacing in
  both JSON and human modes.

- New E2E test class TestE2EConfigUpdateNormalization in tests/test_e2e.py
  exercising the full path against a real Snowflake transformation in
  a dev branch: dry-run preview -> string-script push -> Storage API
  state assert (script[] is list, not str; literal with embedded ;
  preserved) -> --set path with nested script= value -> job run on the
  normalized config (status success/warning, NOT 'Expected array, got
  string' runtime crash). Live-validated against project 901 (padak)
  during development.

## Sync-map walk

Every silent-drift surface from CONTRIBUTING.md "Plugin synchronization
map" updated:

- pyproject.toml 0.27.0 -> 0.27.1; plugin.json + marketplace.json synced
  via make version-sync.
- changelog.py -- five entries for 0.27.1 (fix description, observability,
  silent gap, plumbing, plugin updates).
- commands/context.py AGENT_CONTEXT -- config update bullet annotated.
- commands-reference.md -- config update bullet annotated.
- keboola-expert.md -- Rule 6 VERSION GATE notes 0.27.1+; tool selection
  matrix gains "Update SQL transformation body (script[])" row; inline
  gotchas gain "script[] string-vs-array runtime crash" entry.
- gotchas.md -- new "config update auto-normalizes script[] from string
  to array (since v0.27.1)" section with full failure mode + bypass
  caveat.
- sql-migration-workflow.md -- new error-table row, new "Auto-normalization
  of script[]" section, new anti-pattern.
- SKILL.md regenerated via make skill-gen (no command-name diff; surface
  is signature-stable, behaviour-only change).

## Verification

make check clean (2467 tests pass, lint + format + skill freshness +
version consistency + changelog + error-codes all green). Live e2e
against project 901 confirmed the full path: string-script push -> array
on Storage API -> Snowflake job run to success.

* chore: renumber 0.27.1 -> 0.28.0 (collection branch retarget)

PR base retargeted from main to feat-0.28.0; this fix lands as part of
the 0.28.0 release alongside other v0.28.0-tagged work, not as a
standalone 0.27.1 patch.

Mechanical rename across every silent-drift surface:
- pyproject.toml 0.27.1 -> 0.28.0; plugin.json + marketplace.json
  re-synced via make version-sync.
- changelog.py block key "0.27.1" -> "0.28.0"; the "(since v0.27.1)"
  inside the plugin entry rewritten to "(since v0.28.0)" for consistency.
- gotchas.md, commands-reference.md, sql-migration-workflow.md,
  keboola-expert.md -- all "(since v0.27.1)" / "0.27.1+" markers
  rewritten to 0.28.0.
- commands/context.py AGENT_CONTEXT auto-normalize bullet retagged.
- tests/test_e2e.py docstring + assertion message reference v0.28.0.

No behaviour change. make check clean (2467 tests pass).

* feat(0.28.0): storage swap-tables -- promote typed table rebuild via Storage API swap (#247)

* feat(0.28.0): storage swap-tables -- promote typed table rebuild via Storage API swap

* fix(swap-tables): poll async storage job to completion (PHP claim of sync was wrong)

* fix(swap-tables): register CommandHint so --hint client/service works (PR #247 review B-1)

* fix(0.28.0): VERSION GATE + test client.close() assertion (PR #247 review NB-1, NB-2) (#248)

- keboola-expert.md Rule 6: enumerate `storage swap-tables needs 0.28.0+`
  so an agent on an older install refuses the task before attempting it
  (carry-over NB-1 from PR #247 self-review).
- tests/test_storage_swap.py: add `mock_client.close.assert_called_once()`
  to TestSwapTablesService.test_success and test_api_error_propagates per
  CONTRIBUTING.md Testing Guidelines line 371. Locks the try/finally
  contract that releases the HTTPX client on both happy + error paths.

Documentation/test gaps only -- no functional code changes.

* fix(0.28.0): TestE2EStorageSwapTables must skip without E2E_API_TOKEN (#250)

CI `pytest -m 'not integration'` does not deselect the `e2e` marker, so
the class's setup fixture ran in the runner without credentials and
crashed with KeyError: 'E2E_API_TOKEN'. Other E2E classes apply both
`@skip_without_credentials` (skipif decorator on the class) AND
`@pytest.mark.e2e` (marker for explicit `-m e2e` runs). PR #247
shipped only the marker, hence the rollup PR #249 broke CI.

Verified locally: `pytest -m 'not integration'` now skips the 3 swap
tests cleanly (2480 passed, 69 skipped, 0 errors).

* docs(0.28.0): add typify-table-workflow.md -- typeless to native-types end-to-end (#251)

The PR #247 storage-types-workflow.md gained a small subsection on
'Promoting a typed rebuild back into the original name', but that section
only covered the swap-tables mechanics. AI agents asked to retypify a
Storage table also need to know HOW to profile the data, HOW to choose
types, and HOW to hand off to the user -- those were missing.

New 8-phase workflow:
  0. Decide-or-skip rubric (when typify is the wrong answer).
  1. Isolate in a dev branch (so production stays on the typeless original).
  2. Profile the typeless table in a workspace -- length / cardinality /
     parse-failure / scale-precision queries with a decision matrix that
     maps profile signals to Snowflake native types.
  3. Build typed sibling via storage create-table + copy data via
     in-workspace INSERT or SQL transformation, with row-count and
     NULL-count verification.
  4. Validate downstream consumers in the dev branch (config search +
     representative transformation run against the typeless source).
  5. Swap (dry-run + actual + verify column types swapped).
  6. Re-run downstream as smoke test against the typed source.
  7. Cleanup the sibling after merge.
  8. Handoff protocol -- structured summary the AI agent gives the user
     with phase-by-phase receipts, the merge URL, and rollback / cleanup
     commands.

Plus: SKILL.md workflow-references table gains the new entry; changelog
0.28.0 entry adds a 'Plugin docs' bullet describing the workflow.
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.

1 participant