Context
Surfaced by the iteration-3 convergence reviewer and the /kbagent:review subagent on PR #348 ("feat(0.47.0): fresh-CREATE writeback + semantic-layer reads + sync/storage ergonomics").
When a caller invokes `kbagent storage create-table --if-not-exists` and the table already exists at the expected id, `services/storage_service.py:782-815` returns:
```json
{
"project_alias": "...",
"table_id": "in.c-foo.bar",
"name": "bar",
"bucket_id": "in.c-foo",
"primary_key": [...],
"columns": [...],
"auto_created_bucket": false,
"legacy_branch_storage": false,
"action": "skipped",
"skip_reason": "table already exists"
}
```
The `primary_key` and `columns` fields reflect the USER'S REQUEST (the args passed to the create call), not the existing table's actual schema. A caller that trusts the envelope to discover the real shape would see the wrong values whenever the existing table has a different PK or column set than the caller asked for.
Repro
```bash
kbagent storage create-table --project P --bucket-id in.c-test --name t1 --column id:INTEGER --column name:STRING --primary-key id
returns action:created, columns:[id,name], primary_key:[id]
kbagent storage create-table --project P --bucket-id in.c-test --name t1 --column id:INTEGER --column name:STRING --column DIFFERENT:STRING --primary-key DIFFERENT --if-not-exists
returns action:skipped, columns:[id,name,DIFFERENT], primary_key:[DIFFERENT]
^ but the actual remote table still has columns:[id,name] and primary_key:[id]
```
Fix
When `if_not_exists=True` and we've confirmed the existing table is real via `client.get_table_detail(target_id, branch_id=branch_id)`, return the `columns` / `primary_key` / `name` from that response payload, not from the user's args. The lookup is already happening (`storage_service.py:807`); the response just needs to be re-shaped.
Suggested envelope:
```json
{
"project_alias": "...",
"table_id": "in.c-foo.bar",
"name": "",
"bucket_id": "in.c-foo",
"primary_key": [/* from get_table_detail /],
"columns": [/ from get_table_detail /],
"auto_created_bucket": false,
"legacy_branch_storage": false,
"action": "skipped",
"skip_reason": "table already exists",
"requested_primary_key": [/ user's args /],
"requested_columns": [/ user's args */]
}
```
Optionally surface a warning when the requested and actual shapes diverge — a caller may want to know they hit an existing-but-different table rather than silently moving on.
Why deferred
PR #348 introduces the `--if-not-exists` flag for the primary use case (FIIA `scaffold_storage.py` parallel-worker pattern) where the table is being CREATED for the first time anyway. The mis-reported schema only matters when a caller hits a pre-existing table with a different shape — a real but secondary scenario that's worth its own focused PR rather than expanding the original surface.
Severity
Non-blocking on PR #348 (pre-existing scope of the `--if-not-exists` design surface, not a regression introduced by it). Suitable for a follow-up PR after #348 merges.
Context
Surfaced by the iteration-3 convergence reviewer and the
/kbagent:reviewsubagent on PR #348 ("feat(0.47.0): fresh-CREATE writeback + semantic-layer reads + sync/storage ergonomics").When a caller invokes `kbagent storage create-table --if-not-exists` and the table already exists at the expected id, `services/storage_service.py:782-815` returns:
```json
{
"project_alias": "...",
"table_id": "in.c-foo.bar",
"name": "bar",
"bucket_id": "in.c-foo",
"primary_key": [...],
"columns": [...],
"auto_created_bucket": false,
"legacy_branch_storage": false,
"action": "skipped",
"skip_reason": "table already exists"
}
```
The `primary_key` and `columns` fields reflect the USER'S REQUEST (the args passed to the create call), not the existing table's actual schema. A caller that trusts the envelope to discover the real shape would see the wrong values whenever the existing table has a different PK or column set than the caller asked for.
Repro
```bash
kbagent storage create-table --project P --bucket-id in.c-test --name t1 --column id:INTEGER --column name:STRING --primary-key id
returns action:created, columns:[id,name], primary_key:[id]
kbagent storage create-table --project P --bucket-id in.c-test --name t1 --column id:INTEGER --column name:STRING --column DIFFERENT:STRING --primary-key DIFFERENT --if-not-exists
returns action:skipped, columns:[id,name,DIFFERENT], primary_key:[DIFFERENT]
^ but the actual remote table still has columns:[id,name] and primary_key:[id]
```
Fix
When `if_not_exists=True` and we've confirmed the existing table is real via `client.get_table_detail(target_id, branch_id=branch_id)`, return the `columns` / `primary_key` / `name` from that response payload, not from the user's args. The lookup is already happening (`storage_service.py:807`); the response just needs to be re-shaped.
Suggested envelope:
```json
{
"project_alias": "...",
"table_id": "in.c-foo.bar",
"name": "",
"bucket_id": "in.c-foo",
"primary_key": [/* from get_table_detail /],
"columns": [/ from get_table_detail /],
"auto_created_bucket": false,
"legacy_branch_storage": false,
"action": "skipped",
"skip_reason": "table already exists",
"requested_primary_key": [/ user's args /],
"requested_columns": [/ user's args */]
}
```
Optionally surface a warning when the requested and actual shapes diverge — a caller may want to know they hit an existing-but-different table rather than silently moving on.
Why deferred
PR #348 introduces the `--if-not-exists` flag for the primary use case (FIIA `scaffold_storage.py` parallel-worker pattern) where the table is being CREATED for the first time anyway. The mis-reported schema only matters when a caller hits a pre-existing table with a different shape — a real but secondary scenario that's worth its own focused PR rather than expanding the original surface.
Severity
Non-blocking on PR #348 (pre-existing scope of the `--if-not-exists` design surface, not a regression introduced by it). Suitable for a follow-up PR after #348 merges.