A desktop role writes into the home of the workstation account, chowns to
its name, or becomes it, but nothing made that account exist. Job
108388860157 of run 36235368471 shows what that costs:
Module failed: chown failed: failed to look up user biber
roles/dsk-gnt-claude/tasks/01_gateway.yml:2 path: /home/biber
The round deployed dsk-gnt-claude, dsk-gnt-codex and dsk-code without
user-workstation, and the log had already warned `Unknown user 'biber'`.
tasks/stages/02_workstation.yml was the only guarantee, and it runs for
workstation hosts alone.
The premise is now a service edge. `dsk-base` provides `desktop-base` and
pulls in `user-workstation`; every other dsk role declares that key in its
own meta/services.yml. `_is_service_required` then marks the key required
from the consumer's own declaration and the loader runs both providers in
the constructor stage, before any desktop role. user-workstation becomes a
service at all here: it had no `application_id`, so it was in no registry,
which is what its `# nocheck: required-by-coverage — TODO` recorded.
Measured over every role, HEAD against this tree: exactly the 32 dsk roles
change, each gaining {dsk-base, user-workstation}; no other closure moves
and nothing is removed. For an inventory holding dsk-gnt-claude alone —
the CI constellation — both providers are required and both are in the
whitelist closure, so neither depends on group membership.
The flags stay literal with `# nocheck: dynamic-flag`. The documented
dynamic form is circular for a premise: the edge would only be true once
the provider is already in group_names, and the edge is what puts it there.
Two lints hold the pattern. `services-declared-load` fails an invokable
role that loads a service provider with include_role while declaring no
edge; it found svc-ai-agent-broker, which keeps a nocheck because the edge
would set kata.enabled on a role pinned to the manager, a combination
test_no_placement_manager_with_sandbox rejects as unschedulable.
`desktop-base-edge` fails a dsk role that declares no edge at all. Both
were negative-controlled; the first was vacuous on its first pass, because
iter_project_files_with_content yields absolute paths and the role was
derived from a relative one.
The MCP audit and the alias list follow from the new role and the earlier
i18n-retry target. Disposing of the two new roles is what pulled the audit
into this change, and the audit is why the MCP documents are split here
rather than separately: requirement 035 carried a user story, an
architecture, a per-role audit two lints parse, and 32 acceptance
criteria in one 593-line file. The decision half moves to
docs/architecture/decisions/0003-mcp-surface-per-provider.md, which holds
the forces, the contract, the audit and the consequences; 035 keeps the
user story, the criteria and the order of work. Both link to each other
and state the split: a disposition change belongs in the record, a newly
met criterion in the requirement. The three lints and three cross-
references follow the audit to its new home.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Code
Description
Visual Studio Code in a selectable flavor: the distribution's Code - OSS build, VSCodium, or Microsoft's own binary.
Overview
services.code.flavor picks one of code-oss, vscodium or vscode. The three builds conflict at package level, so the role removes the other two before installing the chosen one.
The role also carries the editor extensions of the agents the same host deploys: when dsk-gnt-claude is in the host's groups, the Claude Code extension is installed into the chosen flavor. Code - OSS and VSCodium resolve extensions through Open VSX, which carries that extension as well.
dsk-gnt-cursor and dsk-gnt-pi carry no entry in that map. Cursor is a full editor rather than an extension, and neither vendor publishes an extension on Open VSX, so there is nothing to install into Code for them. Both agents are used from their own binary.
Features
- Flavor selectable: one setting switches between Code - OSS, VSCodium and the Microsoft build.
- Conflict-free switch: the other flavors are removed first, because they claim the same binary.
- Agent-aware: an agent role on the same host brings its editor extension along.
- Refused early: an unknown flavor aborts the deploy with the list of carried flavors.