Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,28 @@ For detailed SDK migration guides, see [RELEASE_NOTES.md](RELEASE_NOTES.md).

## [Unreleased]

## [0.7.3] — 2026-05-08

`stardag modal deploy` and `stardag modal stardag-api-key create` now
display the slug that actually corresponds to the resolved
workspace/environment UUID. Previously the slug was read from the active
CLI profile's TOML, which could be unrelated to the resolved UUID when
env vars or a custom `config_provider` override the IDs — producing
misleading lines pairing the resolved UUID with a slug from an unrelated
profile. No client-code changes — `pip install -U stardag` is
sufficient. See
[RELEASE_NOTES.md](RELEASE_NOTES.md#v073--correct-slug-display-in-stardag-modal-cli)
for details.

### SDK

- **`stardag/_cli/modal.py`**: replaced `_get_profile_slugs` with
`_resolve_display_slugs`, which reverse-looks up the slug from the
resolved UUID via the id-cache. Slug is omitted when no cache hit
rather than guessing from the active profile.
- **`stardag/config/cache.py`**: added `get_cached_workspace_slug` and
`get_cached_environment_slug` (UUID → slug reverse lookups).

## [0.7.2] — 2026-05-08

`sd.build(resume_build_id=...)` now fires a `BUILD_RESUMED` event so
Expand Down
22 changes: 22 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,28 @@ For changes to the Registry API, UI, and other components, see [CHANGELOG.md](CH

---

## v0.7.3 — Correct slug display in `stardag modal` CLI

Fixes a misleading display in `stardag modal deploy` and
`stardag modal stardag-api-key create` where the workspace/environment
slug printed alongside the resolved UUID could come from a different
profile than the one whose IDs were actually used. The CLI now
reverse-looks up the slug from the resolved UUID via the id-cache, or
omits it when no cache entry exists.

This typically affected setups that override `STARDAG_WORKSPACE_ID` /
`STARDAG_ENVIRONMENT_ID` via env vars or a custom `config_provider`
default factory while leaving an unrelated profile active — for example
hypothetical output like
`Environment: <env-a-uuid> (env-b-slug)`, where the UUID resolves to
environment A but the slug is read from a profile pointing at
environment B. The deployed bytes were already correct; only the
printed slug was wrong.

**No client-code changes** — `pip install -U stardag` is sufficient.

---

## v0.7.2 — Build resume status fix and SKIPPED UI polish

Fixes a UX bug where `sd.build(resume_build_id=...)` silently reused the
Expand Down
74 changes: 43 additions & 31 deletions lib/stardag/src/stardag/_cli/modal.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@

from stardag._cli._helpers import get_authenticated_client
from stardag._cli.credentials import (
get_active_profile,
list_profiles,
list_registries,
resolve_environment_slug_to_id,
resolve_workspace_slug_to_id,
)
from stardag.config.cache import get_cached_slugs
from stardag.config.loader import clear_config_cache, get_config
from stardag.integration.modal import StardagApp

Expand All @@ -61,45 +61,57 @@
app.add_typer(stardag_api_key_app, name="stardag-api-key")


def _get_profile_slugs(
profile_name: str | None,
workspace_id: str | None = None,
environment_id: str | None = None,
) -> tuple[str | None, str | None]:
"""Get workspace and environment slugs from a profile.
def _registry_name_for_url(api_url: str | None) -> str | None:
"""Look up the configured registry name for a given URL.

Returns (workspace_slug, environment_slug) where slug is None if
the profile value matches the resolved ID (i.e. it's already a UUID).
The id-cache is keyed by registry *name* (the TOML key under ``[registry.<name>]``)
rather than URL, so we reverse-lookup the name from the URL the resolved IDs were
fetched against. Returns ``None`` if no registry with a matching URL is configured
(e.g. STARDAG_API_URL is set without a corresponding TOML entry).
"""
if not profile_name:
profile_name_resolved, _ = get_active_profile()
profile_name = profile_name_resolved

if not profile_name:
return None, None

profiles = list_profiles()
prof = profiles.get(profile_name)
if not prof:
if not api_url:
return None
target = api_url.rstrip("/")
for name, url in list_registries().items():
if url.rstrip("/") == target:
return name
return None


def _resolve_display_slugs(
api_url: str | None,
workspace_id: str | None,
environment_id: str | None,
) -> tuple[str | None, str | None]:
"""Reverse-lookup workspace/environment slugs for the resolved UUIDs.

Why: the resolved UUIDs may come from env vars or a custom config_provider
override and have no relation to the active profile's TOML slugs. Reading
`prof["environment"]` could display a slug for a totally different env
than the resolved UUID. We also can't trust ``get_config().context.registry_name``
because callers like ``deploy(--profile foo)`` and ``stardag_api_key_create``
resolve IDs under a temporarily-overridden profile, then restore the env
before this function runs — leaving ``get_config()`` pointing at the
*active* profile, which may use a different registry. Pin the registry to
the URL the IDs were actually resolved against.
"""
registry_name = _registry_name_for_url(api_url)
if not registry_name:
return None, None

ws_slug = prof["workspace"] if prof["workspace"] != workspace_id else None
env_slug = prof["environment"] if prof["environment"] != environment_id else None
return ws_slug, env_slug
return get_cached_slugs(registry_name, workspace_id, environment_id)


def _print_stardag_context(
env_vars: dict[str, str],
profile_name: str | None = None,
) -> None:
def _print_stardag_context(env_vars: dict[str, str]) -> None:
"""Print stardag context info (registry, workspace, environment, target roots)."""
import json

registry = env_vars.get("STARDAG_API_URL", "none (local mode)")
ws_id = env_vars.get("STARDAG_WORKSPACE_ID", "N/A")
env_id = env_vars.get("STARDAG_ENVIRONMENT_ID", "N/A")

ws_slug, env_slug = _get_profile_slugs(profile_name, ws_id, env_id)
ws_slug, env_slug = _resolve_display_slugs(
env_vars.get("STARDAG_API_URL"), ws_id, env_id
)

console.print(f"[dim] Registry: {registry}[/dim]")

Expand Down Expand Up @@ -297,7 +309,7 @@ def stardag_api_key_create(
if api_key_name is None:
api_key_name = f"modal-{modal_env or 'default'}"

ws_slug, env_slug = _get_profile_slugs(stardag_profile, ws_id, env_id)
ws_slug, env_slug = _resolve_display_slugs(api_url, ws_id, env_id)
ws_display = f"{ws_id} ({ws_slug})" if ws_slug else ws_id
env_display = f"{env_id} ({env_slug})" if env_slug else env_id

Expand Down Expand Up @@ -561,7 +573,7 @@ def deploy(
console.print(f"[cyan]Using stardag profile: {profile}[/cyan]")
env_vars = get_profile_env_vars(profile)
if env_vars:
_print_stardag_context(env_vars, profile)
_print_stardag_context(env_vars)
extra_secrets.append(
modal.Secret.from_dict(dict(env_vars)) # type: ignore[arg-type]
)
Expand Down
26 changes: 26 additions & 0 deletions lib/stardag/src/stardag/config/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,32 @@ def cache_environment_id(
save_id_cache(cache)


def get_cached_slugs(
registry: str,
workspace_id: str | None,
environment_id: str | None,
) -> tuple[str | None, str | None]:
"""Reverse lookup: cached (workspace_slug, environment_slug) for the given UUIDs.

Loads the id-cache once. Returns ``(None, None)`` for inputs without a cache hit.
"""
cache = load_id_cache()
ws_slug: str | None = None
if workspace_id:
for slug, ws_id in cache.workspaces.get(registry, {}).items():
if ws_id == workspace_id:
ws_slug = slug
break
env_slug: str | None = None
if workspace_id and environment_id:
envs = cache.environments.get(registry, {}).get(workspace_id, {})
for slug, env_id in envs.items():
if env_id == environment_id:
env_slug = slug
break
return ws_slug, env_slug


def _looks_like_uuid(value: str) -> bool:
"""Check if a string looks like a UUID."""
uuid_pattern = r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
Expand Down
Loading