Commit Graph
4 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5 65d226d0d9 fix(container): reach every resolver address through a shell, and lint for it
The container_address lookup does not return a container name in swarm. It
returns a shell fragment, "$(/usr/bin/resolve-container-id <stack> <service>)",
which is only a name once an interpreter evaluates the substitution. The shell
module provides one. The command module does not -- it execs the binary directly,
so the fragment arrives at container exec as literal text and the call dies with
"No such container: $(...)".

Compose hides the entire class: there the same lookup yields a bare name and
command: works. The failure exists only in swarm, and no swarm run had touched
these paths -- both were added by feature commits (b23653adf8, 0421ebeb16) that
wrote the same argv shape independently, which is the argument for a rule rather
than six edits.

Six call sites moved to the shell module: the moodle egress task, the three
moodle AI-gateway tasks, and the two wordpress AI-Engine tasks. The wordpress
pair keeps its stdin, so it uses the dict form with cmd:. Moving off argv exposes
the surrounding values to word splitting, so they now go through quote -- the
address itself must NOT, because single quotes would suppress exactly the
substitution this depends on.

test_argv_exec_needs_shell.py closes the gap that let this through.
container-exec-resolver checks that the resolver is used; it has nothing to say
about whether anything can evaluate the result. The new rule checks that other
half. It reuses the sibling's argv parser, which recognises only container/docker
in first position, so an argv list that does start an interpreter
(argv: [bash, -lc, ...], as joomla does) is correctly not matched.

Run against the tree the rule reported exactly the five sites found by hand plus
bigbluebutton, and passed over joomla and the discourse task, whose address is a
config lookup rather than the resolver. Bigbluebutton is pinned to compose via
compose_mode_force and can only ever see a bare name, so it takes a nocheck.

Five container-exec-resolver markers disappear with the argv form: under shell
the resolver is the ordinary path, not an exception.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 11:39:38 +02:00
kevinveenbirkenbachandClaude Opus 5 2aec4326a8 fix(roles): follow the php scripts to their new folder
The relocation in dfe7a7fdd moved provision_mcp.php,
provision_mcp_identity.php and the ai_engine pair into files/php/ but left
their callers pointing at the old locations, so moodle's and snipe-it's MCP
provisioning and wordpress's AI Engine configuration resolved a path that
no longer exists.

The reference sweep that accompanied the move searched for python, shell,
ruby and sql folders and never looked for php, so these four lookups were
missed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 12:04:52 +02:00
kevinveenbirkenbachandClaude Opus 5 8e8dbbd08c refactor(layout): fold the prefix-shared AI files into their own folders
test_prefix_groups_belong_in_subfolders flagged three groups added by this change
set: a shared delimiter-bounded prefix is a folder in disguise, so the group moves
into a folder named after the prefix and the prefix leaves the file names.

  sql/select/ai-{bot-user,service}.sql.j2  -> sql/select/ai/{bot-user,service}.sql.j2
  files/provision_{ai,mcp}.py              -> files/provision/{ai,mcp}.py
  files/ai_engine_{configure,endpoint}.php -> files/ai_engine/{configure,endpoint}.php

All six are recorded as renames, so history follows them. Every reference moves
with them: four templated query paths in mattermost's ai_gateway.yml, the two
lookup('file', ...) calls in n8n, the two in wordpress, and SCRIPT_PATH plus the
module docstring in the n8n unit test. lookup('file', ...) resolves relative to
the role's files/ directory, so the new argument is provision/mcp.py rather than
a repo-relative path.

Worth recording why this survived every gate: make quality was green on each run
this session, including immediately before the commits that introduced these
files. The lint enumerates through git ls-files, and the files were untracked, so
it could not see them. Committing them is what made them visible. A green gate
taken before new files are tracked says nothing about the gate after.

Verified: repository lints go from 88 passed with 1 failure to 89 passed, the
unit suite passes at 4448, and a repo-wide grep for every old path returns
nothing.
Not verified: no deploy has run. The lookup('file', ...) arguments and the
templated SQL paths resolve at play time, so a wrong path would surface as a task
failure rather than a test failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 15:19:26 +02:00
kevinveenbirkenbachandClaude Opus 5 0421ebeb16 feat(wordpress): bind the AI Engine connector to the platform gateway
WordPress has no AI surface in core, so the requirement applies to whichever
connector the role pins rather than leaving the choice to a site administrator.
The pinned connector is ai-engine, whose `custom` engine type takes an
OpenAI-compatible base URL and an API key per environment.

AI Engine keeps every setting in a single mwai_options array and its REST route
replaces that array wholesale, so the deploy merges instead of assigning:
ai_engine_configure.php filters ai_envs by id, re-appends the managed
environment, and leaves every other environment and API key untouched. A
straight write would delete configuration this deploy never owned.

Both scripts are fed to `wp eval-file -` over stdin with their input passed as
an environment variable. The first attempt used `wp eval` with the code inline,
which cannot survive the quoting: a YAML folded scalar hands Jinja a literal
backslash sequence and PHP fails to parse. That was reproduced in the container,
along with the detail that `wp eval-file -` requires the opening <?php tag,
before the approach was changed. Passing the payload as base64 in an env var
means no shell quoting touches either the code or the virtual key.

ai_engine_endpoint.php reads back the endpoint AI Engine actually stored for the
managed environment, so the assertion compares the plugin's own state rather
than the payload the deploy just sent.

Not verified: the configure script has never run against a live AI Engine
install, so the merge is reasoned from the option shape rather than observed,
and addons/ai-engine.spec.js has executed zero times because the addon bridge
gate resolved svc-ai-litellm against the Playwright role list and skipped it.
ai-engine is third-party, so a change to its option schema would leave this
writing a shape the plugin no longer reads.

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