Skip to content

semantic-layer model delete leaves orphan children → blocks Build/Import retries #306

Description

@padak

Bug

kbagent semantic-layer model delete --project <p> --model <m> removes the
model from the metastore but leaves every child entity (semantic-dataset,
semantic-metric, semantic-relationship, semantic-constraint, semantic-glossary)
intact
. Because dataset names are unique per project in the metastore, the
next Build or Import that emits a dataset with the same name fails with
HTTP 422 "semantic-dataset with name 'X' already exists in the target model"
even though the user just deleted what they think was the only owner.

Reproduction (no fork, ran on padak-2-0-master)

# 1) Build a small model
kbagent semantic-layer build --project padak-2-0-master \
  --tables "in.c-keboola-ex-db-mysql.addresses" --name temp_model

# 2) Delete it via the documented command
kbagent semantic-layer model delete --project padak-2-0-master --model temp_model --yes
# → "Deleted model temp_model (019e36dd-7edb-...)"

# 3) Verify orphans on the wire
TOKEN=$(jq -r '.projects."padak-2-0-master".token' ~/.config/keboola-agent-cli/config.json)
curl -s -H "X-StorageApi-Token: $TOKEN" \
  'https://metastore.keboola.com/api/v1/repository/semantic-dataset' \
  | jq '.data[] | select(.attributes.name=="addresses")'
# → still returns an item whose modelUUID points at the deleted model

# 4) Trying to rebuild now fails:
kbagent semantic-layer build --project padak-2-0-master \
  --tables "in.c-keboola-ex-db-mysql.addresses" --name another_model
# → "build_model push failed at datasets/'addresses': API error 422 ...
#    semantic-dataset with name 'addresses' already exists in the target model"

Where it lives

services/semantic_layer_service.py::SemanticLayerService.delete_model
(parent UUID -> client.delete_item("semantic-model", model_uuid)).
The service correctly lists children via _fetch_children_parallel to
populate orphaned_children in the response envelope, then deletes only
the model itself. The children become wire-level orphans.

Suggested fix

In delete_model, delete each child before deleting the parent, in the
reverse of PUSH_ORDER:

for type_name in reversed(PUSH_ORDER):  # constraints → glossary → ... → datasets
    for child in children.get(type_name[0], []):
        client.delete_item(type_name[1], child["id"])
client.delete_item("semantic-model", model_uuid)

Return shape stays the same; the existing orphaned_children counts
become a record of what was cascaded rather than what was leaked.

Severity

Medium. Workflow-breaking for the Build / Import flows users will hit
most often. Workaround today is to enumerate semantic-* repository
endpoints and delete by UUID, which is exactly what no normal user will
do unaided.

Discovered during

UI smoke test of the new Semantic Layer page (feat/semantic-layer-ui)
on padak-2-0-master. Surfaced after the new http_base error-body
parser started rendering the real metastore message instead of bare
"API error 422: 422" — this bug was already there, just invisible.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions