This repository was archived by the owner on May 13, 2026. It is now read-only.
feat(ingestion): surface schema-service 409 as typed conflict, file dev cleanup tooling - #1029
Merged
Merged
Conversation
When the schema service refuses a proposed schema because its `descriptive_name` already maps to a different Approved canonical (the new 409 introduced upstream in schema_service PR #145 — kanban #49603), the message needs to reach the user as "There's already a schema called 'Photography' on this node. Rename your new schema or reuse the existing one." — not as "Schema creation error: Failed to create schema via schema service: schema service refused duplicate descriptive_name …" wrapped twice. Adds `IngestionError::SchemaDescriptiveNameConflict { descriptive_name, existing_canonical, reason }` with a `user_message()` arm that produces the rename / reuse copy. `schema_creation::create_new_schema_with_node` detects the conflict via the message sentinel emitted by `schema_service_client::add_schema` and converts it to the typed variant before propagating. The detection is string-based on purpose: the fold_db_node pinned rev of schema_service doesn't have the new error message yet (the bump cascade lands it on its 2h schedule). On the current rev, the detection is a no-op — the legacy "Schema service returned unexpected CONFLICT (409)" message doesn't match — and the existing `IngestionError::SchemaCreationError` path stays. Once the cascade bumps the rev, the new message text starts arriving and the typed variant fires automatically. A follow-up PR will swap to the typed `AddSchemaOutcome::Conflict` enum once that's available. Tests: - `parse_schema_name_conflict_recognises_the_canonical_message` pins the sentinel + parsing against the exact message shape `add_schema` produces. - `parse_schema_name_conflict_ignores_unrelated_errors` keeps transport / 5xx errors flowing through the existing path. - `schema_descriptive_name_conflict_user_message_is_actionable` pins the UI-facing copy. Filed kanban follow-ups: prod-cleanup (separate scope; needs deliberate data preservation plan) and concurrent-Lambda race prevention (needs backend-level atomicity — DynamoDB conditional writes / per-name lease). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Companion to schema_service PR #145 (https://github.com/EdgeVector/schema_service/pull/145), which stops two Approved schemas from ever sharing a
descriptive_nameby returning 409 with a typedDescriptiveNameConflictbody instead of silently growing a duplicate canonical.This fold_db_node-side PR makes that 409 reach the user cleanly:
IngestionError::SchemaDescriptiveNameConflict { descriptive_name, existing_canonical, reason }variant, with auser_message()arm that says:schema_creation::create_new_schema_with_nodedetects the conflict via the canonical message sentinel emitted byschema_service_client::add_schemaand converts to the typed variant before propagating.SchemaCreationErrorwrapping is unchanged.AddSchemaOutcome::Conflictenum becomes available in the client; a follow-up PR will swap to typed detection.Investigation findings (Phase 1 of kanban #49603)
Live dev snapshot of
https://y0q3m6vk75.execute-api.us-west-2.amazonaws.com/v1/schemas/available(940 schemas, 2026-05-13) found 11descriptive_namecollision groups includingPhotography x3,Journal Entries x3,Vehicle x2and 8 more — all active, nonesuperseded_by. Root cause:state::add_schemahad three sites (state.rs:1144 / 1230 / 1300pre-patch) that detected adescriptive_namematch, ranis_cross_schema_type_expansion, and silently fell through to persist a fresh canonical when the schema_types didn't match. PR #1028's 409-at-the-query-boundary papered over the symptom; schema_service PR #145 fixes the cause.Dev cleanup
schema_service PR #145 also ships a new admin endpoint
POST /v1/admin/dedupe-descriptive-namesthat walks the registry, keeps the schema with the largest field set as the survivor perdescriptive_namegroup, and marks the restsuperseded_by = survivor. Idempotent. Data preserved.Operator runbook (run once on dev after schema_service PR #145 deploys):
Out of scope
AddSchemaOutcome::Conflictonce the bump cascade lands the new schema_service rev.Test plan
cargo clippy --workspace --all-targets -- -D warningscargo fmt --all --checkcargo test --lib ingestion::error(3 new tests green:parse_schema_name_conflict_recognises_the_canonical_message,parse_schema_name_conflict_ignores_unrelated_errors,schema_descriptive_name_conflict_user_message_is_actionable)POST /v1/admin/dedupe-descriptive-nameson dev once, confirms 0 collisions remain.schema_type→ 409 from schema_service surfaces asIngestionError::SchemaDescriptiveNameConflictwith the rename prompt copy.🤖 Generated with Claude Code