Module migration

Module migration 

Source
Expand description

One-shot startup migration that seeds a “Default” identity bundle from ambient OAuth credentials living in the user’s home dir (<HOME>/.<auth_dir_name>/.credentials.json for each oauth-class provider).

Per SPEC_OAUTH_IDENTITY_BUNDLES_2026_05_22.md §5 (OAuth Bundles PR E): when a user upgrades from a pre-bundle build that wrote OAuth tokens to the legacy ambient location (e.g. Claude Code’s ~/.claude/), AgentMux should auto-detect those credentials and bind them into a “Default” identity bundle so launches keep working without forcing the user to re-OAuth. Empty / "blank" identity_id rows on db_agent_instances are back-filled to point at the new Default bundle so the resolver picks them up at next spawn.

§Idempotency contract

The migration is safe to call on every srv startup:

  1. For every oauth-class provider in the registry we check whether ANY identity bundle already has a binding for that provider. If yes → that provider is already covered (either by a prior run of this migration, or by a user-driven auth.start flow), skip it.
  2. The “Default” bundle is upserted (not unconditionally INSERT’d) — running the migration twice produces no extra row.
  3. The IdentityAccount uuid is reused on the re-bind path (bundle_identity_bindings lookup), matching the persist_oauth_binding_or_synthetic pattern from PR C, so a second run doesn’t orphan rows in db_identity_accounts.
  4. Back-fill only runs when the Default bundle exists OR was created this run — so a fresh install with no ambient creds never rewrites identity_id to a non-existent FK.

Failure modes are warn-don’t-block (same as inject_identity_env): account upsert / bind / publish errors are logged and skipped, the srv keeps coming up.

Structs§

MigrationStats
Summary of what the migration did. Returned for testability + log observability; the production caller only inspects field counts (e.g. logging “0 providers seeded” at info level for visibility).

Constants§

DEFAULT_BUNDLE_ID
Id used for the seeded Default bundle. Fixed so the migration is idempotent across restarts (a second run looks up by id, sees the row, reuses it instead of minting a new uuid). Not the same as the "blank" singleton — the blank bundle has no bindings by contract.
DEFAULT_BUNDLE_NAME
Human-readable name for the seeded bundle. Per spec §5.1.

Functions§

bound_providers 🔒
Collect the set of providers that are bound in ANY identity bundle today. The migration’s idempotency gate — if a provider is already in here, the ambient-creds seed is a no-op for that provider.
ensure_default_bundle 🔒
Look up the Default bundle by id; create it (via bundle_identity_upsert) if missing. Returns true when a new row was inserted, false when an existing row was reused.
run_default_bundle_migration
Entry point — call once on srv startup, after WaveStore is open and before the srv begins accepting requests. home_dir_override is None in production (resolves dirs::home_dir()); tests use Some(tempdir) so they can plant fake ~/.<auth_dir_name>/.credentials.json files without touching the user’s real home.