Commit Graph
5 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5 5324eadd8b fix(ai): resolve the gateway backend from the inventory, not from one host
LITELLM_BACKEND_ROLES read lookup('deployment').groups, which deployment.py
fills from group_names. That variable is host-scoped: it lists only the groups
the host currently being processed belongs to.

On a swarm round the manager carries svc-ai-ollama, svc-ai-lmstudio and
svc-ai-litellm while the workers carry three groups and none of them is an AI
backend. Both branches of LITELLM_CHAT_MODEL therefore missed on a worker and,
with OPENROUTER_API_KEY empty, the model resolved to the empty string.

Only one task made that visible. Every stateful task in the gateway include
carries delegate_to STACK_HOST plus run_once, so it templates in the manager's
scope and wrote the correct model into the database. The assert carries
neither, so it re-templated in each worker's own scope and failed there while
the manager passed.

The manager-only membership is not a placement decision. The swarm test
inventory is provisioned with a single --host MGR, so every role of the round's
closure gets that one host, and extend_inventory then widens only
svc-swarm-node, svc-storage-nfs-client, svc-storage-nfs-server and the round's
own application. The three AI roles declare no placement at all; they are
manager-only because nothing widened them.

Add an inventory selector next to the host-scoped one, built from the play-wide
groups magic var, skipping groups without hosts and Ansible's implicit all and
ungrouped, sorted so the cache key is stable. The key takes it too, or two
hosts with equal group_names but different inventories would share an entry.

The blast radius is two readers, both in 16_ai.yml, and both ask only whether
svc-ai-ollama or svc-ai-lmstudio is present. On the manager the new value gains
exactly svc-storage-nfs-server and svc-bkp-nfs-2-local, neither of which either
reader tests, so the manager's answer does not move and only the workers change,
to match it.

This is the third step on the same line. 46a16127fc set .running; b149ad0d22
corrected that to .groups because a targeted apps= round leaves only the
command-line roles in the whitelist. .groups solves the targeted round and not
the multi-host one, because group_names stays host-scoped either way.

Measured in a two-host play with a real group_vars file and the real lookup:
the worker resolves ollama false through group_names and true through the new
selector, the manager is true both ways, and the assert that failed in CI
passes on both hosts.

Requirement 036 named the host-scoped backend selection as the thing that has
to change and deferred it. That residual is closed, so the paragraph now
describes the selector in use and why the two rejected ones are wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 19:28:44 +02:00
kevinveenbirkenbachandClaude Opus 5 2d30681977 docs(mcp): turn the audit into something the lints can check
The audit read as prose, so a role could sit under a classification nobody had checked against its own declaration, and "blocked" could not be told apart from "nobody looked". The sections are now named after the exact values a role declares, which makes the section titles the vocabulary: one rule holds every role under its own declared classification, a second rejects a classification outside the vocabulary, and a third requires a named blocker per blocked role while forbidding a blocked role from shipping an enabled surface.

The report itself is generated from the roles' own metadata and a drift check imports the generator and compares against the committed file, which caught a stale report twice while this work was in progress. It lives under docs/contributing rather than beside the requirements, because a generated file in that directory reads like a finished criterion.

The criteria across seven requirement files now carry what they rest on: what closed them, or what blocks them and where that was measured. Two coverage limits stay stated rather than hidden: the report is built only from roles that declare an MCP block, so a role blocked without one is invisible to it, and a ticked criterion here means implemented and tested rather than proven in operation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-29 00:58:44 +02:00
kevinveenbirkenbachandClaude Opus 5 fbb68ed0f4 docs(036): file the three candidates on what the running instances offer
Criterion 1 asks for each unverified candidate to be probed against a running
instance and filed with its reason. Replace the vendor-documentation claim
with what was measured:

- baserow 2.3.3 reads settings.BASEROW_OPENAI_BASE_URL in
  generative_ai_model_types.py:271, so the AI field takes a custom
  OpenAI-compatible base URL. Confirmed.
- homeassistant 2026.7 has no base_url in openai_conversation's config flow
  (0 matches). Its only configurable local path is the separate ollama
  integration, whose config flow carries CONF_URL (7 matches) but speaks
  Ollama's native API rather than the gateway's OpenAI-compatible surface.
  Excluded.
- xwiki carries no AI or LLM extension in the pinned image's extension
  repository. Left open rather than excluded: the extension is an installable
  first-party one, like mediawiki's AIEditingAssistant, so excluding it would
  be scope reduction.

Also correct the backend-role rationale. The text claimed .running is right
because group_names is per host and the two agree on a single-host round.
They do not once apps= is set: backends arrive as dependencies, so both
branches missed and the gateway answered every prompt with
`Invalid model name passed in model=openrouter/auto`.

No checkbox is ticked: criterion 1 wants every candidate filed, and xwiki is
still open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 12:43:39 +02:00
kevinveenbirkenbachandClaude Opus 5 507e3f4337 docs(036): record the gateway integration in the requirement and the matrix
The requirement corrects two rows against what the pinned releases actually
expose: Moodle 4.5 configures aiprovider_openai per action rather than through a
single base URL, and n8n 1.95.3 reads its endpoint from a stored credential,
after N8N_INSTANCE_AI_MODEL_URL turned out not to exist in that release. espocrm
and peertube move into the exclusion table with measured evidence rather than
vendor claims, and the variant-coverage section records which round exercises the
gateway.

Criteria 2, 5, 8 and 9 are ticked. Criteria 3, 4, 6 and 7 stay open on purpose:
the wiring is complete across all nine confirmed roles, but only zammad's spec
has been observed answering a prompt through the gateway, and the last deploy
ended before Playwright ran. Criterion 1 is still open because homeassistant,
xwiki and baserow have not been probed.

The 027 generator gains litellm as a framework key and litellm and n8n on the
web-app axis, so every consumer's gateway edge appears in the matrix. Its
regenerated output is committed alongside it; the generator writes the file in
place rather than to stdout, and a fresh run reproduces the committed matrix
byte-for-byte. Touching the generator also brought its comment history into the
lint's scope, so about thirty section banners and narration lines go with it.

The espocrm README carries the exclusion evidence -- at tag 10.0.4 only Crm ships
under application/Espo/Modules, the config metadata declares no AI key, and the
release ZIP leaves custom/Espo/Modules empty -- plus four steps for enabling the
commercial Intelligence extension once licensed. A version bump does not help:
upstream documents that extension as requiring 10.0.3 or greater, so newer cores
host it rather than absorb it. The provider field names are only discoverable by
unpacking the purchased artifact, and the note says so rather than guessing.

The root README and the litellm README are generated; both now show the gateway
among the dependencies of its eight consumers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 15:15:15 +02:00
kevinveenbirkenbachandClaude Opus 5 953eddfe27 docs(requirements): require native AI surfaces to use the platform gateway
svc-ai-litellm serves an OpenAI-compatible gateway and web-app-nextcloud is
already bound to it, but that binding exists once and is written down
nowhere. A sweep over all 85 application roles finds provider references in
nine, five of which are AI-native, so every other application with a native
AI feature still defaults to a vendor.

Record which roles have a surface with a configurable endpoint, which are
excluded and why, and which vendor claims still need a probe against a
running instance. Two criteria close classes that cost a full session to
diagnose: a deploy must fail when an enabled AI surface carries an empty
key, and no credential schema may encode a vendor key format —
web-app-matrix validates ^sk-[a-zA-Z0-9]{40,}$ today and would reject a
gateway-issued key by construction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 15:41:10 +02:00