You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Every alert Datum sends to Slack carries a catalog button. For some services it lands the responder on a search page instead of the service.
The alert named that service by a name the catalog has retired.
Backstage can resolve the name when the button is clicked, rather than the sender guessing in advance which names will resolve.
Acceptance criteria
A request naming a retired service reaches the service that absorbed it
A request naming a current service reaches that service
A name the catalog cannot place falls back to search, as today
The alert sender no longer needs to know which names resolve
How it works today
The sender decides. The button carries a list of the services the catalog defines, generated from the catalog and committed, so it holds the current names and never the retired ones. A retired name misses the list and goes to search, even though the catalog can place it.
Retired names keep resolving on purpose. Routing honors them so a rename never sends an alert to the wrong room, and the link is the only thing that degrades.
The list also goes stale on any change to the set of services, including a rename that changes nothing else. That has broken the check twice in one evening, and it is a standing trap for whoever renames a service next.
Notes
Retired names are first-class in the catalog. Each service records the names it used to carry, and those names already resolve an owning team. This asks the entity lookup to use the same list the ownership resolution already uses.
The catalog already holds the answer. Every retired name a service absorbs is emitted as a tag on that service's System entity, alongside a "Formerly" sentence in the description, so control-plane carries activity-compliance, catalog-search and notifications today. Nothing reads those tags when a link is built, because the link is built by the sender.
The plan is a resolver route in the app, no backend change. A frontend module mounts a page at /s, and /s/<name> asks the catalog for a System whose metadata.name or metadata.tags matches the name, in one request with the two filters ORed. An exact name match wins. A single tag match resolves to the service that absorbed the name. Anything else redirects to /search?query=<name>, which is where those names land today.
A backend route was the other option and is worse here. It needs its own auth policy to answer an unauthenticated click, and it would edit the same backend entrypoint that #82 is already changing. The frontend route rides the session the browser already has, which is the same session the current button relies on.
Once this ships, the alert sender stops choosing. The button becomes a link to /s/ plus whatever service the alert carries, and the generated list of current service names in the infra repository can go, along with the three-place wiring that keeps it from going stale. That is a separate pull request in that repository, after this one.
Two limits worth recording. Tags are lowercased and stripped of anything outside [a-z0-9-] when they are generated, so a future retired name carrying a dot or a capital would not match the label the alert sends. Every retired name in the catalog today is already lowercase and hyphenated, so nothing is broken now, and an explicit annotation would be the durable key if that changes. The other is that tags on a System mean "retired name" only because this catalog generates every System from one source. A hand-written System elsewhere with an unrelated tag could collide, which is why an exact name match takes precedence and two tag matches fall back to search rather than guessing.
The generator already refuses to map one retired name onto two services, so the ambiguous case should not reach the resolver at all.
Activity
[-]Alert links land on search, not the service[/-][+]Catalog button lands on search[/+]ecv commentedon Sep 17, 2026
The catalog already holds the answer. Every retired name a service absorbs is emitted as a tag on that service's System entity, alongside a "Formerly" sentence in the description, so
control-planecarriesactivity-compliance,catalog-searchandnotificationstoday. Nothing reads those tags when a link is built, because the link is built by the sender.The plan is a resolver route in the app, no backend change. A frontend module mounts a page at
/s, and/s/<name>asks the catalog for a System whosemetadata.nameormetadata.tagsmatches the name, in one request with the two filters ORed. An exact name match wins. A single tag match resolves to the service that absorbed the name. Anything else redirects to/search?query=<name>, which is where those names land today.A backend route was the other option and is worse here. It needs its own auth policy to answer an unauthenticated click, and it would edit the same backend entrypoint that #82 is already changing. The frontend route rides the session the browser already has, which is the same session the current button relies on.
Once this ships, the alert sender stops choosing. The button becomes a link to
/s/plus whatever service the alert carries, and the generated list of current service names in the infra repository can go, along with the three-place wiring that keeps it from going stale. That is a separate pull request in that repository, after this one.Two limits worth recording. Tags are lowercased and stripped of anything outside
[a-z0-9-]when they are generated, so a future retired name carrying a dot or a capital would not match the label the alert sends. Every retired name in the catalog today is already lowercase and hyphenated, so nothing is broken now, and an explicit annotation would be the durable key if that changes. The other is that tags on a System mean "retired name" only because this catalog generates every System from one source. A hand-written System elsewhere with an unrelated tag could collide, which is why an exact name match takes precedence and two tag matches fall back to search rather than guessing.The generator already refuses to map one retired name onto two services, so the ambiguous case should not reach the resolver at all.