# mvirt 0.15.0

migrations: yes
wire: unchanged
rev: 02adc9678247cf95c403de40b53ec3f474a9436d

v0.15.0 — one infra project, not two

Every installation carried two service-provider projects and could explain neither. This
release makes them one: the project the ADR-0068 control-plane edge lives in and the
ADR-0051/0056 home of every system app are the same row, seeded at boot and adopted by the
manifest.

What changed

- **There is one infra project.** It used to be two — `platform`, seeded at boot so the edge
  had somewhere to live before the first-run wizard, and the manifest-named `projectSlug`,
  home of the system apps. The timing problem that produced the split is real: the edge
  serves the console the wizard runs in, so it needs a project before a manifest can supply
  one. The conclusion drawn from it was not. Both rows were created in `org-infra` with
  `service_provider = true` and both meant "the platform's own project"; what an operator
  saw was a second project holding a handful of certificates and one service account per
  control-plane member, with nothing on any surface to explain it.
- **The project is resolved, not named.** `bootstrap::infra_project` answers by the
  `service_provider` flag rather than a fixed id, and `systemapp::infra_project` and the
  export's private resolver collapse onto it. The answer no longer requires a stored
  document — which is precisely what made the edge need a project of its own.
- **`infra.projectSlug` is retired.** Parsed and ignored, never re-emitted, and gone from the
  setup wizard. Deliberately not removed from the schema outright: every manifest written
  before this carries the key, and `deny_unknown_fields` would turn an operator's own stored
  seed into one that 400s on the next PUT.
- **`ensure_infra` re-asserts the boot seed** rather than requiring it to have run. A
  reconciler may not depend on another step's ordering (CLAUDE.md); the first cut did, and
  eight bootstrap tests said so.

Upgrading

Nothing to do. m0136 folds a legacy `platform` row's contents into the infra project and
deletes the emptied shell — the edge's certificates and its per-member agent credentials
move; a tenant project is untouched. It moves rows rather than renaming a project, because
a project id is the foreign key of 27 entity tables plus `Account.home_project`. An
installation whose only service-provider project IS the legacy one keeps it, id and all.

This is a MINOR because it migrates the store: ADR-0069 D9 requires that a patch be undoable
by booting the previous generation, and a migrated store forbids that. The control-plane
host writes its pre-migration backup automatically; rollback is store restore.


## Upgrading

Point your flake's `mvirt` input at this release and redeploy the control-plane
host first, then each node in turn:

```
nix run .#colmena -- apply boot --reboot --on <control-plane host>
nix run .#colmena -- apply boot --reboot --on <node>   # one at a time
```

**This release migrates the store.** Once the control plane has started, the
previous version will refuse to run against it — migrations are forward-only, so
rolling the control-plane host back means restoring a backup, with data loss back
to the backup point. The control plane writes a backup immediately before it
applies migrations; nodes roll back freely by booting the previous generation.
