containers: drive user provisioning and remounts from the system bus
Nightly Build / build (push) Successful in 6m51s

The endpoints that changed a user or a membership row also reached into
ContainerManager themselves: users_mgmt called ensure()/remove(), and both
shared_folders and projects called refresh_user_mounts through a local
remount() helper. Every future endpoint that grants membership would have
had to remember to do the same.

Announce instead. SystemEventBus gains UserCreated / UserDeleted /
UserMountsChanged, emitted after the DB write, and one subscriber —
wiring::spawn_user_lifecycle — does the Docker work: sequentially (which
serialises concurrent operations on the same container), best-effort by
contract (the row is already committed, so a hiccup settles at the user's
next login or at boot reconciliation), and holding only a Weak<Skald>. It
is spawned after construction, like set_skald, because it reacts through
Skald's own accessors.

Also fixes a real gap the event makes impossible to repeat: the web setup
wizard created the first admin without provisioning a container. It runs
against a live server, where reconcile_all() has already happened, so that
admin had no sandbox until the next restart. It now emits UserCreated like
any other creator; the console shell needs no equivalent, since it runs
before the server and boot reconciliation covers it.

Two behaviour changes: POST /api/users and POST /api/projects no longer
wait on Docker before responding. Provisioning was already best-effort, and
a new project's folder is still created synchronously, so the explorer —
which reads host-side — shows it at once; only execute_cmd reachability
lands a moment later.
This commit is contained in:
2026-07-26 21:57:25 +01:00
parent cf5415ae88
commit c50a0d84da
8 changed files with 153 additions and 45 deletions
+21
View File
@@ -54,6 +54,27 @@ pub enum SystemEvent {
SessionCancelled {
session_id: i64,
},
// ── User lifecycle (blueprint §6) ─────────────────────────────────────────
// Announced by whoever changed the row; the reaction — provisioning, tearing
// down or remounting a Docker container — belongs to the lifecycle reconciler
// in `skald-core`, never to the endpoint that made the change.
/// A user was created, by any creator (the Users admin page, the first-run
/// setup wizard). Their execution sandbox has to be provisioned.
UserCreated {
user_id: String,
},
/// A user was deleted. Their sandbox has to be torn down.
UserDeleted {
user_id: String,
},
/// A user's **mount topology** changed — a shared-folder or project membership
/// was granted, revoked or re-graded (RO ⇄ RW). Their container must be
/// recreated against the new mount set, and a live session's filesystem view
/// refreshed with it.
UserMountsChanged {
user_id: String,
},
}
// ── Bus ───────────────────────────────────────────────────────────────────────