fix(storage): write column descriptions where UI and MCP read them (#624) - #631
Conversation
…olumn patch (#624) Review follow-ups on PR #631. - serve: add POST /storage/columns/{project}/describe-migrate, the 1:1 mirror CONTRIBUTING.md requires (its three describe-* siblings already had routes, so external callers got a 404). No `yes` field -- a REST caller opting into the write IS the confirmation; `dry_run` is the shared preview. Two router tests cover scope/flag forwarding and the empty-body default. - e2e: assert a sibling column keeps its description across a partial write. describe-batch sends `id` alone, so `name` (described two steps earlier) must survive; the PUT .../definition `columns` payload is a positive-only patch and nothing asserted that before. - e2e: seed the legacy migration key on `value`, the one undescribed column -- the real pre-0.88.0 shape. Seeding it on `name` made the live run report `conflict` (correctly: the column already had a native description), which the old assertion mistook for a migration failure. The conflict rule now has its own explicit assertions.
Live verification (project 5946, Snowflake)Ran the E2E suite against a real stack. The storage path is now verified end-to-end, not just mocked. Full suite: 93 passed, 13 skipped, 9 failed — none of the failures are regressions from this PR:
Re-run of Positive-only column patch (the review question) was confirmed by a separate live probe: described three columns, re-described one, the other two kept their descriptions verbatim. Details in the resolved review thread. Remaining unverified: nothing on the write path. The |
75aaaf7 to
6864360
Compare
…olumn patch (#624) Review follow-ups on PR #631. - serve: add POST /storage/columns/{project}/describe-migrate, the 1:1 mirror CONTRIBUTING.md requires (its three describe-* siblings already had routes, so external callers got a 404). No `yes` field -- a REST caller opting into the write IS the confirmation; `dry_run` is the shared preview. Two router tests cover scope/flag forwarding and the empty-body default. - e2e: assert a sibling column keeps its description across a partial write. describe-batch sends `id` alone, so `name` (described two steps earlier) must survive; the PUT .../definition `columns` payload is a positive-only patch and nothing asserted that before. - e2e: seed the legacy migration key on `value`, the one undescribed column -- the real pre-0.88.0 shape. Seeding it on `name` made the live run report `conflict` (correctly: the column already had a native description), which the old assertion mistook for a migration failure. The conflict rule now has its own explicit assertions.
Rebased onto main (#629 landed first)
No version bump. Both branches had independently claimed 0.88.0; since 0.88.0 is unreleased, this PR's changelog notes now sit under the existing 0.88.0 key alongside #621's, and
UI verification is also done: all three column descriptions render on the Schema tab of a live table (project 5946), confirming an independent consumer sees what the CLI wrote. |
…ient methods (#624) PUT /v2/storage/branch/{branch}/tables/{id}/definition (async, waits for the tableDefinitionUpdate storage job; production uses the literal branch ref "default", matching the web UI client) and the synchronous metadata entry DELETE. Fixes the set_table_metadata docstring: the flat KBC.column.{name}.description convention is legacy, invisible to the Keboola UI and MCP server, kept only for migration and read fallback.
…tail fallback (#624) describe-column/describe-batch write through the native definition endpoint with isDescriptionSystemManaged=false, fail fast on unknown columns, and auto-migrate remaining legacy flat entries on the touched table (conflict -> skip, orphan -> skip, migrated entries deleted so a later clear cannot be resurrected by the read fallback). New storage describe-migrate command converts legacy entries in bulk (dry-run-then-confirm, per-table error accumulation, --prune-orphans). table-detail resolves descriptions native definition -> columnMetadata KBC.description (alias source fallback) -> legacy flat key and reports legacy_column_descriptions with a human-mode warning; reads never write. Descriptions panel moved to commands/_storage_describe.py and the service logic to services/_column_descriptions.py to hold the file-size budgets (snapshot-module precedent).
Version 0.88.0 + changelog; AGENT_CONTEXT, CLAUDE.md command list, commands-reference, gotchas (since v0.88.0), storage-describe-workflow, regenerated SKILL.md decision table, keboola-expert one-line trigger (61991/62000 bytes).
…olumn patch (#624) Review follow-ups on PR #631. - serve: add POST /storage/columns/{project}/describe-migrate, the 1:1 mirror CONTRIBUTING.md requires (its three describe-* siblings already had routes, so external callers got a 404). No `yes` field -- a REST caller opting into the write IS the confirmation; `dry_run` is the shared preview. Two router tests cover scope/flag forwarding and the empty-body default. - e2e: assert a sibling column keeps its description across a partial write. describe-batch sends `id` alone, so `name` (described two steps earlier) must survive; the PUT .../definition `columns` payload is a positive-only patch and nothing asserted that before. - e2e: seed the legacy migration key on `value`, the one undescribed column -- the real pre-0.88.0 shape. Seeding it on `name` made the live run report `conflict` (correctly: the column already had a native description), which the old assertion mistook for a migration failure. The conflict rule now has its own explicit assertions.
6864360 to
64c915d
Compare
Problem
storage describe-column/describe-batchstored column descriptions as flatKBC.column.{name}.descriptionkeys in table metadata — a convention nothing but kbagent itself ever read. Documented columns looked blank in the Keboola UI, were invisible to the MCP server'sget_table_detail/search, and never reached SnowflakeCOMMENT/ BigQuery column descriptions. The failure was silent in both directions becausetable-detailread back its own convention.Analysis of
keboola/uiandkeboola/connection(details in the issue comment): the UI writes throughPUT .../tables/{id}/definitionand the backend mirrors that write intocolumnMetadataKBC.description— one native write is visible to the UI, the MCP server, and the warehouse. The metadata-only fix suggested in the issue would have left descriptions out of the warehouse and unprotected from Output Mapping overwrite (isDescriptionSystemManagedstaystrue).Changes
Write path —
describe-column/describe-batchnow write via the native definition endpoint:PUT /v2/storage/branch/{branch}/tables/{id}/definitionwithcolumns: [{name, description}]+isDescriptionSystemManaged: false(async; the CLI waits for thetableDefinitionUpdatestorage job)Read path —
table-detailresolves each column description with precedence:definition.columns[].definition.description(aliases:sourceTable.definition)columnMetadata[col]KBC.description(aliases:sourceTable.columnMetadata, MCP-server parity)KBC.column.*plus an always-present
legacy_column_descriptionslist and a human-mode warning pointing atdescribe-migrate. Reads never write.Migration — new
storage describe-migrate --project ALIAS [--table-id ID ...] [--bucket-id ID] [--prune-orphans] [--dry-run] [--yes] [--branch ID]:--prune-orphansClient (layer 3) —
update_table_definition+delete_table_metadataonKeboolaClient;set_table_metadatadocstring corrected (the "no user-writable column-metadata endpoint" claim was wrong).Structure — the Descriptions command panel moved to
commands/_storage_describe.pyand the service logic toservices/_column_descriptions.py(file-size budgets; follows the_storage_snapshots.pyprecedent).Docs — 0.88.0 + changelog; AGENT_CONTEXT, CLAUDE.md command list, commands-reference, gotchas (since v0.88.0), storage-describe-workflow rewritten, SKILL.md regenerated, keboola-expert one-line trigger (61 991 / 62 000 bytes).
Testing
make checkgreen: 5738 passed, 12 skipped (lint, format, changelog, skill, sentinel-guard, loc checks included)--deny-writes_test_storage_describeextended with a native round-trip assertion and a seeded-legacy-key migrate cycle (requiresE2E_API_TOKEN/E2E_URLto run)Closes #624