Submit an issue View all issues Source
MIR-1271

miren app history never shows who deployed — deployer identity is captured nowhere

In Progress public Bug
phinze phinze Opened Jun 29, 2026 Updated Sep 10, 2026

The "DEPLOYED BY" column in miren app history (and the "Deployed By" line in miren app status) is always blank, rendering as - for every deployment on every cluster. Confirmed live on garden for rfd and meet: git info populates fine, deployer is empty across the board.

Root cause

The display side is correct. The CLI reads DeployedByUserName/Email/Id and falls back to - when they're empty (cli/commands/app_history.go, formatUser). They're always empty because the server never records who deployed. All three deployment-creation paths in servers/deployment/server.go leave the user fields blank: CreateDeployment hardcodes empty strings with a // will be implemented with auth integration TODO (the integration was never done), and DeployVersion and createEnvVarDeployment only set the timestamp.

A bit of archaeology (re: MIR-498)

MIR-498 "Deployed By Should Show User Email Instead of User XID" is marked Done, but it never actually worked. Commit ffaed260 "fixed" it by setting userName := "testuser" server-side, so the column showed the literal string "testuser" on every deploy. That was close enough to "not blank" to close the ticket. Then 9b29530a ("Improve CLI output for app history and whoami") swapped the placeholder for empty strings and rewrote the comment to // leave empty - the CLI display will show "-" for unknown users. So today's - is the deliberate, documented output of that commit. Real identity capture has never existed. This issue supersedes MIR-498.

What identity is actually available (we dug into this)

The cloud JWT the runtime verifies carries only sub = user XID (usr-xxxx), organization_id, and group_ids. No email, no name, anywhere in the token (cloud/services/middleware/jwt.go:281). The XID is the trustworthy, unforgeable identifier available server-side via rpc.IdentityFromContext(ctx). Turning an XID into an email requires a cloud round-trip, and cloud currently has no endpoint that lets a cluster resolve another user's XID → email (a cluster service account can only look up a user's groups). The CLI knows its own email via /api/v1/me, but identity should be server-derived from the JWT, not asserted by the client.

Fix (two phases)

Phase 1 — capture (runtime only, ship first). In all three creation paths, read rpc.IdentityFromContext(ctx) and store Subject into DeployedBy.UserId (plus Method and organization_id for context). Delete the empty-string hack and the now-dead unknown@example.com/user@example.com sentinel checks. The column starts showing the authoritative XID for new deploys. Honest, unforgeable, and a prerequisite for Phase 2 (can't resolve what we never stored). This also gives the OIDC/CI path a real identity (its sub is repo:org/app:ref:...).

Phase 2 — resolution (cloud + runtime). Add a cloud endpoint for org-scoped batch XID → {email, name} resolution callable by cluster service accounts (the org boundary makes it safe). Resolve at display time in ListDeployments/GetActiveDeployment with a short TTL cache, not at write time. Display-time resolution retroactively fixes every historical record (no backfill possible otherwise), always reflects current email, and keeps the stored record anchored to the stable XID. The CLI's existing email → name → id → - fallback lights up automatically.

Note: Phase 1 alone shows opaque usr-xxxx, which is barely more useful to a human than -. The real win (the thing that prompted this) needs Phase 2, so "done" = both phases.

Related small fix to fold in

miren whoami mislabels identity: cli/commands/whoami.go:106 does output.UserEmail = claims.Subject, but sub is the user XID, not an email (the runtime's own parser even comments // The subject field is the user ID in miren JWTs, pkg/auth/parse_unverified.go:38). So whoami has been printing the XID under a field it calls email. Worth correcting alongside this, and it benefits from the same Phase 2 resolution.

Relationship to MIR-681

This is independent of MIR-681 (server-owned deployment lifecycle) and shouldn't wait on it. MIR-681 fixes correctness of state transitions; this fixes a field captured nowhere — a flawless deploy still records no deployer. Phase 1 is forward-compatible: when MIR-681 moves record creation server-side, the identity-capture one-liner just moves with it.