- Status: done
- Date: 2026-05-30
- Specs touched: docs/specs/BRAIN_HOST_PROTOCOL.md
Added GET /v1/users/{username}/home → { home_path, uid, gid } to host-agent. The brain runs containerized and has no access to /etc/passwd; this endpoint lets it resolve the owner's home directory and POSIX UID/GID so that slice 4 (writeOverride) can emit correct bind-mount sources and user: directives for personal-scope app instances.
Code changes:
internal/protocol/host.go— newResolveHomeResponse{HomePath string, UID int, GID int}with JSON tagshome_path/uid/gid.internal/hostagent/agent.go—ErrUnknownUsersentinel;ResolveHome(user string) (home string, uid, gid int, err error)added to theUserManagerconsumer-side interface;resolveHomehandler registered inMount; fake branch returns/home/<username>+ stable UID/GID in [3000, 3999] from FNV-32a hash of the username; 404 withunknown-userwhen the real manager returnsErrUnknownUser.internal/hostagent/usermgr/linux.go—ResolveHomeimplemented onLinuxUserManagerviaos/user.Lookup; mapsuser.UnknownUserErrortohostagent.ErrUnknownUser; parsesu.Uid/u.Gidviastrconv.Atoi.internal/hostclient/hostclient.go—ResolveHome(ctx, username)method callingGET /v1/users/<url-escaped-username>/home.internal/lifecycle/docker.go—ResolveHome(ctx, user)added toHostDriverinterface (consumed by writeOverride in slice 4).- Tests:
internal/hostagent/agent_test.go(fake branch stable/deterministic, delegate path, 404 shape, 500 shape);internal/hostclient/hostclient_test.go(round-trip over UNIX socket + 404 error path);internal/lifecycle/fakes_test.go(fakeHost.ResolveHomestub to satisfy updated interface).
Realizes BRAIN_HOST_PROTOCOL.md # User info endpoints (new section). The pattern (Pattern A sync, typed 404 with code field) mirrors the credential-mutation siblings. ErrUnknownUser follows the "typed errors at boundaries" rule from CLAUDE.md: the brain needs to discriminate unknown-user (install error) from host unreachable (retry).
cmd/host-agent-real/main.go wires a.UserMgr = &usermgr.LinuxUserManager{} — ResolveHome is automatically live in the real binary once LinuxUserManager implements the interface, which it now does. No main changes needed.
The well-known box constants slice 4 also needs (malmo_app_uid/malmo_app_gid/malmo_shared_gid for household-scope instances) are deferred to slice 4, which can add GET /v1/identity/well-known or fold them into an existing summary endpoint. Flagged in slice 4's scope.
- Slice 3 —
GET /api/v1/catalog/{id}/install-plan: read-only endpoint that returns permission lines, role-derived scope options, per-folder source options, and pick-subfolder prompts. No host call needed; reads from the parsed manifest. - Slice 4 —
writeOverride+writeEnvenforce permissions:user:, folder bind mounts from elected source,group_addfor shared, devices, GPU,MALMO_FOLDER_*injection. CallsResolveHome(this slice). Decide whetherGET /v1/identity/well-knownbelongs here or can use hard-coded defaults. - Slice 5 — consent + config UI in
web-ui/src/views/StoreView.vue.