fix: clear NotFoundError on tag-cache miss instead of (DELETED) sentinel (BLDX-1530)#971
Conversation
… sentinel (BLDX-1530)
When a tag name couldn't be resolved in the local cache, AtlanTagRetranslator
substituted the literal `(DELETED)` sentinel into the request and sent it to
Atlas, which rejected it with an opaque `ATLAS-404-00-008: Given classification
(DELETED) was invalid` — naming neither the tag nor the cause, and misleading
customers into thinking a tag was deleted when it may never have existed
(Multiplex Sev1, ZD#126376: add_atlan_tags(["Alert: DQ"]) on a tenant without
that tag).
On the asset-tag write path (classification objects), an unresolvable name now
raises NotFoundError naming the tag ("Atlan tag with name 'Alert: DQ' does not
exist"). The `(DELETED)` sentinel itself is preserved so an asset read with an
already-deleted tag still round-trips losslessly; the purpose/persona policy
(names) path stays tolerant for the same round-trip reason. Fixed in both the
sync and async retranslators.
Adds unit tests (sync + async: resolve / raise-on-missing / sentinel round-trip)
and an integration test (add_atlan_tags with a non-existent tag raises the named
error).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e); consolidate tests Refine BLDX-1530: get_id_for_name returns None for both a soft-deleted tag and a name that never existed, but the cache tracks deleted names separately (deleted_names). Use it so the retranslator only RAISES for a name that never existed; a known-deleted tag (or the (DELETED) sentinel from a read round-trip) keeps the sentinel — lossless, since the tag really existed. Applied to both the sync and async retranslators. Move the retranslator unit tests into tests/unit/test_atlan_tag_name.py (+ aio), alongside the existing tag-serde tests, and drop the standalone test_retranslators files. Adds the deleted-tag-keeps-sentinel case so both behaviours are covered. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…g-cache-miss raise (BLDX-1530)
Live testing on a real tenant exposed that the deleted_names-based
distinction never worked: AtlanTagCacheCommon.get_id_for_name_after_refresh
adds ANY unresolved name to deleted_names on a refresh miss ("could be an
audit-log entry"), so a never-existed name is in deleted_names by the time
the retranslator checks it. The guard therefore never raised, and
add_atlan_tags(["<missing>"]) still shipped the (DELETED) sentinel to Atlas,
reproducing the opaque "ATLAS-404-00-008: Given classification (DELETED)".
The cache genuinely cannot tell a soft-deleted tag from a never-existed one
by name, so the only reliable "genuinely deleted" signal is the (DELETED)
sentinel *string* itself, produced by a read of a deleted tag. Gate on that:
preserve the sentinel (lossless round-trip); raise a clear, named
NotFoundError (ATLAN-PYTHON-404-006) for any other unresolvable name.
Verified live on all three cases: never-existed -> named 404-006 raise;
active add -> succeeds; deleted-tag read -> (DELETED) preserved. Unit test
now pins that deleted_names membership does NOT suppress the raise (regression
guard); integration test asserts the specific 404-006 message.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Update: live testing changed the approachValidated the earlier Root cause of the miss: Fix: the cache can't tell the two apart by name, so the only reliable "genuinely deleted" signal is the Live validation (all three cases, on a real tenant):
Unit test now pins that |
What & why
Fixes BLDX-1530 (Multiplex Sev1, ZD#126376). When a tag name can't be resolved in the local tag cache,
AtlanTagRetranslatorsubstituted the literal(DELETED)sentinel into the request payload and sent it to Atlas, which rejected it with an opaque:That names neither the tag nor the cause, and misleads customers into thinking a tag was deleted when it may simply never have existed — the repro is
add_atlan_tags(["Alert: DQ"])on a tenant without that tag.The fix
On the asset-tag write path (classification objects:
classifications/addOrUpdateClassifications/removeClassifications), an unresolvable tag name now raises a clear client-side error naming the tag:Two behaviors are deliberately preserved:
(DELETED)sentinel round-trip — an asset read with an already-deleted tag surfaces(DELETED); re-serializing it must stay lossless, so a name that is the sentinel is preserved (not raised on).classificationNames/purposeClassificationsstay tolerant (they round-trip deleted references through the sentinel); the strict check applies only to the asset-tag write path where the customer-facing bug occurs.Fixed in both the sync (
retranslators.py) and async (aio/retranslators.py) retranslators.Tests
tests/unit/test_retranslators.py+aio/): resolve existing tag → id; missing tag → namedNotFoundError(no sentinel leak);removeClassificationsmissing → raises;(DELETED)sentinel round-trip preserved; names/policy path stays tolerant.tests/integration/test_client.py::test_add_nonexistent_classification_raises_named_error):add_atlan_tagswith a non-existent tag raises the named error end-to-end.Related: CSA-491 (the customer package crash), DOC-815 (same opaque symptom for playbooks).
Closes BLDX-1530.