---
id: w2/m9/t002
title: bex-api — mint installation token → clone Secret on create/deploy/webhook-redeploy
worker: worker2
status: done
estimate: 45m
depends_on: [w2/m9/t001]
---

## Objective

When a deploy-triggering verb (Create/upsert, MCP `deploy`, webhook redeploy) targets an App whose `spec.repo` belongs to the workspace's GitHub connection, bex-api mints a fresh installation token (m8 client), writes/refreshes the `<app>-clone` Secret in the App's namespace, and sets `spec.cloneSecret` — so every build starts with a token minted seconds ago.

## Context

- Verb sites: `lego/backend/internal/apps` — `Create`/`Deploy` (docs/ADR017-deploy-from-chat.md decisions 2–4) and the unexported `redeploy` the webhook calls (`webhook.go`).
- Repo-ownership check: match the App's canonicalized repo URL against the connection's repos (reuse the webhook's repo canonicalization); public repos not in the connection keep today's behavior (no secret).
- Secret write follows the existing bex-api k8s-writes pattern (env-vars materialization, [docs/ADR013-secrets.md](../../../docs/ADR013-secrets.md)); label it managed-by bex-api for cleanup on service delete (delete-cascade — check the w2/m4 orphan list).
- Upsert discipline: `cloneSecret` is create-owned only in this flow — don't clobber a hand-set value pointing elsewhere.

## Steps

1. Helper in `internal/apps` (taking the m8 github service): resolve repo → installation, mint, upsert Secret, set `spec.cloneSecret`.
2. Call it from Create/upsert + redeploy; token-mint failure = clean 502-ish error naming GitHub, not a silent public-clone fallback for private repos.
3. Service delete removes the clone Secret (extend the cascade; note in parity/delete docs).
4. Unit tests with a fake github client: secret written + spec set; refresh on redeploy; non-connection repos untouched; mint failure surfaces.

## Files

- `lego/backend/internal/apps/` (create/deploy/webhook paths) + tests

## Acceptance criteria

- [ ] Private-connection repo deploys get a fresh Secret + `cloneSecret` on every trigger, including webhook redeploys
- [ ] Public/unconnected repos: behavior unchanged
- [ ] Delete cascade covers the clone Secret

## Out of scope

- Long-lived token refresh for slow operator retries (accepted 1h limitation, ADR'd in m8/t001)
