The project licence governs this repository's code and says nothing about the application a role installs, which is where a restriction on commercial use usually sits: a deployment cleared to use Infinito.Nexus commercially is still barred from NLLB-200's CC BY-NC weights.
Each invokable role names its licence in meta/<role>/license.yml with the url it was read from. The rights of a named licence live once in meta/licenses.yml, fifteen fields following the taxonomy choosealicense publishes. AND folds to the strictest combination, OR keeps the most favourable whole licence, and the seven contexts are derived from the rights rather than declared, so a heading cannot contradict the matrix under it.
The generated page renders one matrix per context, a lint refuses a role that declares nothing, and a Playwright spec asserts the page against the deployed docs. 21 of 22 deprecated SPDX ids were resolved against the upstream text; EPSON's remains unread because its AUR page blocks automated access.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
aceb5295ef moved Cosmos, Quick Setup and Credits out of the committed role
READMEs and into the documentation build's own scratch checkout, so a
committed copy can no longer go stale when the metadata moves. Merging
feature/designer (ebad39422c) took origin/main's side of those files, which
still carries them, and reinstated all 290 along with the cosmos make target
and its i8cos alias. tests/lint/ansible/roles/test_readme.py hard-fails on a
committed copy, and the reinstated Credits block credited one person with two
URLs, which tripped the credits-attribution lint as well.
Stripped with the repository's own fence-aware parser against
MANAGED_SECTIONS, not with a line filter: the Cosmos block holds a mermaid
fence a naive filter would cut through. The result is byte-identical to
aceb5295ef for every README that carries no prose change since.
The cosmos target wrote the forbidden section straight back into the
checkout, so it goes with them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
53c120aaac paired "become: false" with "become_user" on the desktop roles.
Setting become to false means do not escalate, so the sibling become_user is
inert and the task runs as the connection user: pipx installed the Blender MCP
server into /root and the editor extension landed in the profile of root. The
package install inside the addon file now pins become_user to root explicitly,
because it would otherwise inherit the workstation user from the apply block of
the include and run apt unprivileged.
Two consequences of the same chain ship with it, because no half of it leaves a
deployable state on its own:
- The registration inferred an installed editor from services.code.enabled, but
dsk-code declares nothing for code-oss, vscode or vscodium on Debian and
RedHat, so no editor can exist there and the command exited 2. It now probes
the binary and registers only when it resolves.
- The CLI test resolved the server through PATH and read the config under HOME
while running as root. It now uses absolute paths rendered from the role vars
and reads the registry claude writes as a file, the way the test of
dsk-gnt-claude already does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The role carried a primary entry with modes, flavor and limits but never declared itself the provider of the code service, so a role referencing it found nobody registering it. Continues fc5ad87dc5.
Registering turns the role into one load/app.yml pulls in dynamically, and that path checks run_once_dsk_code before including it. With the flag set at the end of 00_core.yml a second load attempt would still see it absent and include the role again, so it moves to the first task as 3bfeba1243 did for the other shared providers.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
Cosmos, Quick Setup and Credits follow from meta/services.yml, categories.yml
and galaxy_info.author, yet 279 role READMEs carried a copy that went stale
whenever the metadata moved and nobody re-ran make docs. The documentation
build now runs the generators itself, against its own scratch checkout, and
the committed READMEs keep only the prose a person writes.
utils.cache.PROJECT_ROOT derives from the module's location, so putting the
checkout first on PYTHONPATH makes the generator operate on the scratch tree.
i18n.py and builder.py add it; the one remaining coupling, a relative_to over
PROJECT_ROOT in a log line, is gone.
CI extracted the "### Production" block from the committed README with awk.
Stripping the section made guide_deployable() return "" for every role, which
selects no deploy row and turns nothing red: a fail-open regression. It now
asks _is_role_invokable, which matches the old condition exactly (178 roles
either way, both difference sets empty), and cli/build/docs/readme/production.py
renders the block for the two guide scripts. All 178 renders are byte-identical
to what HEAD served, and both scripts' extraction pipelines produce identical
output for a web app, a service and a desktop role.
test_readme.py inverts: the three sections are now a hard failure in a
committed README. readme_md.md documents what each is derived from.
Removed: the docs, cosmos, readme-generate, readme-index and readme-check
targets, the five aliases pointing at them, and docs from make quality.
Verified by running generate_commands() in series against a copy of the
checkout: every command exits 0, no README is left without Cosmos or Credits,
and exactly the 178 invokable roles get a Quick Setup. The ansible_roles step
needs pandoc, which only the docs image carries, so that one link of the chain
is unexercised here; it is unchanged by this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
make docs output, from the quality-high pass that follows the seven commits of
this branch. Every edge here is a declaration those commits made, drawn rather
than written.
dsk-code gains claude and codex as dependencies and their service nodes, which
is its meta/services.yml at bond 0.5. The two agent roles gain the mirror image,
svc-ai-litellm above them and dsk-code below, so the provider and consumer halves
that landed in separate commits appear together for the first time. pi gains only
the gateway, having no editor extension to be depended on for.
The root index fills the dependency column for the three agents, gives Codex its
first row, and puts a video link on System One from the info.yml entry.
svc-ai-litellm/README.md is absent because 843631ffa5 already carried it. The
tree now holds no reference to svc-ai-jeff.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
b958322400 kept the extension catalogue in vars/main.yml as code_agent_extensions,
a map from role id to extension id filtered through group_names. That put a
catalogue in vars/ where every other role keeps one in meta/addons/, and it knew
only Claude.
The fact splits three ways now, each owned once. meta/services.yml states
whether the host deploys the agent, and is the only place group_names appears.
meta/addons/<id>.yml states the extension id and defers its gate to that flag
through lookup('config'), which is what the addon contract expects and what
test_addon_enabled_no_group_names enforces: an addon testing group_names itself
restates a fact the owning role already carries. vars/main.yml reads the addons
back, and 00_core.yml loops them.
The loop evaluates item.value.enabled | bool in its when rather than filtering
with selectattr. An addon's enabled survives as Jinja text, so selectattr would
read the string 'False' as true and install an extension the host did not ask
for.
codex joins Claude. open-vsx.org serves openai.chatgpt from a verified openai
namespace as 'Codex, OpenAI's coding agent', so the id resolves on the Open VSX
registry that Code - OSS and VSCodium query, not only on the Microsoft one.
Cursor and pi stay out, as b958322400 recorded: neither Anysphere nor
earendil-works publishes there.
The two tasks/addons/ hooks are empty because the generic install loop is the
whole integration; test_addons_integration_tasks requires the file to exist for
a service-coupled addon, and web-app-nextcloud/tasks/addons/deck.yml is the same
shape.
Verified: 396 lints with 363 subtests, including the three that this change
turns green, and the extension ids resolved against the Open VSX API.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every desk- role becomes dsk-, and the three coding agents move to dsk-gnt- under a category of their own. The category carries a title, hanzi and icon but no invokable flag: an invokable child under an invokable parent is rejected outright, which is also why web itself is not invokable.
Three things a search and replace would have broken without a sound. get_entity_name now strips dsk-gnt-, so the primary entity keys agnt-claude, agnt-cursor and agnt-pi no longer resolved and all three roles dropped out of the CI plan. Eighteen run_once guards carried the old name against a convention its own test enforces. DESKTOP_ROLE_PREFIX in the addon spec lint is a literal, so the desktop exemption it grants would have matched nothing and the lint would have moved to the wrong rule in silence.
docs/requirements/035 is not a historical document but a register a lint holds to every role with an application_id, so its list carries the new ids. In 026 only the links move; the prose stays as a statement about the tree at the time it was written.
Measured against the plan with the rename normalised, the deployed role#variant set is unchanged. A stale id in an inventory is caught before any deploy starts: the pre-flight validator reports Invalid group 'desk-firefox' (not in application_ids).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>