mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
feat(xwiki): declare the AI LLM extension as a gateway consumer
xwiki was listed in requirement 036 as an unverified candidate. Probing the
running instance showed no AI or LLM extension in the extension repository
of the pinned lts-postgres-tomcat image, so there was no surface to point at
the gateway. Rather than excluding the role, wire the extension the same way
mediawiki wires AIEditingAssistant.
The declaration follows the nine existing consumers: a litellm service block
bound to 'svc-ai-litellm' in group_names, a sk_prefixed litellm_api_key, the
gateway pinned on variant 2 as 036 prescribes for three-variant roles, and an
integration hook that pulls the shared consumer contract.
meta/addons/ai-llm.yml feeds the existing installer: xwiki_enabled_install_items
turns enabled addons into the {id, version} list that the Groovy extension
installer consumes.
Two limits, deliberately not papered over. The maven coordinate
org.xwiki.contrib.llm:application-ai-llm-ui at 0.7 is not verified against
the upstream repository, so a wrong coordinate fails loudly on first deploy.
And the spec asserts the extension is installed and the gateway host stays
undotted, not that a prompt is answered: the extension's configuration model
has to be measured on a running container before that wiring can be written
without guessing. xwiki therefore does not yet satisfy criterion 6.
This role has never been deployed; only the gate has run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
90ff555603
commit
2383311b7c
8 files changed
+93
-2
No files matched your search
@@ -15,6 +15,7 @@ The diagram places XWiki in the Infinito.Nexus cosmos: the components it deploys
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph deps [Dependencies]
|
||||
dep_svc_ai_litellm["svc-ai-litellm 🐳🐝"]
|
||||
dep_svc_bkp_volume_2_local["svc-bkp-volume-2-local 💻"]
|
||||
dep_svc_db_openldap["svc-db-openldap 🐳🐝"]
|
||||
dep_svc_db_postgres["svc-db-postgres 🐳🐝"]
|
||||
@@ -29,6 +30,7 @@ flowchart LR
|
||||
dep_web_svc_logout["web-svc-logout 🐳🐝"]
|
||||
end
|
||||
subgraph role [web-app-xwiki 🐳🐝]
|
||||
svc_litellm["litellm"]
|
||||
svc_sso["sso"]
|
||||
svc_logout["logout"]
|
||||
svc_ldap["ldap ❌"]
|
||||
@@ -46,6 +48,7 @@ flowchart LR
|
||||
subgraph dependents [Dependents]
|
||||
dpt_web_app_nextcloud["web-app-nextcloud 🐳🐝"]
|
||||
end
|
||||
dep_svc_ai_litellm -. "0..1" .-> svc_litellm
|
||||
dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
|
||||
dep_svc_db_openldap -- "0..0" --> svc_ldap
|
||||
dep_svc_db_postgres -. "0..1" .-> svc_postgres
|
||||
@@ -58,8 +61,8 @@ flowchart LR
|
||||
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
|
||||
dep_web_svc_css -. "0..1" .-> svc_css
|
||||
dep_web_svc_logout -. "0..1" .-> svc_logout
|
||||
svc_sso -. "0..1" .-> dpt_web_app_nextcloud
|
||||
linkStyle 1 stroke:red;
|
||||
svc_litellm -. "0..1" .-> dpt_web_app_nextcloud
|
||||
linkStyle 2 stroke:red;
|
||||
```
|
||||
|
||||
Solid `1:1` edges are fixed relationships; dashed `0..1` edges are conditional (enabled only in matching deployments); red `0..0` edges are turned off in this role. Node markers show the role's deploy modes (💻 host, 🐳 compose, 🐝 swarm); ❌ marks a service that is explicitly turned off, and ⚙️ an Ansible role dependency declared in `meta/main.yml`.
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
const { test, expect } = require("@playwright/test");
|
||||
const { resolveTimeout } = require("../timeouts");
|
||||
|
||||
const { skipUnlessAddonEnabled } = require("../addon-gating");
|
||||
const {
|
||||
decodeDotenvQuotedValue,
|
||||
normalizeBaseUrl,
|
||||
gotoOnion,
|
||||
} = require("../personas");
|
||||
|
||||
test.use({ ignoreHTTPSErrors: true });
|
||||
|
||||
const appBaseUrl = normalizeBaseUrl(process.env.APP_BASE_URL || "");
|
||||
const aiGatewayBaseUrl = normalizeBaseUrl(process.env.AI_GATEWAY_BASE_URL || "");
|
||||
const superadminUsername = decodeDotenvQuotedValue(process.env.XWIKI_SUPERADMIN_USERNAME || "");
|
||||
const superadminPassword = decodeDotenvQuotedValue(process.env.XWIKI_SUPERADMIN_PASSWORD || "");
|
||||
|
||||
const EXTENSION_ID = "org.xwiki.contrib.llm:application-ai-llm-ui";
|
||||
|
||||
test("ai-llm: the AI LLM Application extension is installed on the wiki", async ({ page }) => {
|
||||
skipUnlessAddonEnabled("ai-llm");
|
||||
test.setTimeout(resolveTimeout(120_000));
|
||||
|
||||
expect(appBaseUrl, "APP_BASE_URL must be set").toBeTruthy();
|
||||
|
||||
const gatewayHost = new URL(aiGatewayBaseUrl).hostname;
|
||||
expect(
|
||||
gatewayHost.includes("."),
|
||||
`the gateway must be addressed by its bare in-cluster service name; "${gatewayHost}" carries a dot, so the container dns-search suffix completes it and prompts leave the deployment through the public ingress`,
|
||||
).toBe(false);
|
||||
|
||||
expect(superadminUsername, "XWIKI_SUPERADMIN_USERNAME must be set").toBeTruthy();
|
||||
expect(superadminPassword, "XWIKI_SUPERADMIN_PASSWORD must be set").toBeTruthy();
|
||||
|
||||
const installed = await page.request.get(
|
||||
`${appBaseUrl}/rest/extensions/${encodeURIComponent(EXTENSION_ID)}`,
|
||||
{
|
||||
headers: { Accept: "application/json" },
|
||||
params: { media: "json" },
|
||||
failOnStatusCode: false,
|
||||
timeout: resolveTimeout(60_000),
|
||||
},
|
||||
);
|
||||
|
||||
expect(
|
||||
installed.status(),
|
||||
`the deploy installs ${EXTENSION_ID} through the Extension Script Service; a 404 means the addon never reached the wiki, so no AI surface exists to point at the gateway (HTTP ${installed.status()})`,
|
||||
).toBe(200);
|
||||
|
||||
await gotoOnion(page, `${appBaseUrl}/`, { waitUntil: "domcontentloaded" }).catch(() => {});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
enabled: "{{ lookup('config', 'web-app-xwiki', 'services.litellm.enabled') | bool }}"
|
||||
required: false
|
||||
mechanism: extension
|
||||
source: upstream
|
||||
bridges:
|
||||
- litellm
|
||||
version: "0.7"
|
||||
group: ai-llm
|
||||
config:
|
||||
id: org.xwiki.contrib.llm:application-ai-llm-ui
|
||||
@@ -1,5 +1,10 @@
|
||||
---
|
||||
credentials:
|
||||
litellm_api_key:
|
||||
description: Per-consumer virtual key for the shared svc-ai-litellm gateway
|
||||
algorithm: sk_prefixed
|
||||
validation:
|
||||
min_length: 50
|
||||
superadminpassword:
|
||||
description: Password for the xwiki superadmin
|
||||
algorithm: alphanumeric
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
---
|
||||
litellm:
|
||||
bond: 0.5
|
||||
enabled: "{{ 'svc-ai-litellm' in group_names }}"
|
||||
shared: "{{ 'svc-ai-litellm' in group_names }}"
|
||||
# nocheck: seaweedfs-required — object storage not implemented for this role
|
||||
sso:
|
||||
bond: 1
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
# variant pinning every dynamic services.yml flag to true so the
|
||||
# matrix exercises the in-group_names branch of web-app-xwiki.
|
||||
- services:
|
||||
litellm:
|
||||
enabled: false
|
||||
shared: false
|
||||
container_backup:
|
||||
enabled: true
|
||||
dashboard:
|
||||
@@ -35,6 +38,9 @@
|
||||
tor:
|
||||
enabled: true
|
||||
- services:
|
||||
litellm:
|
||||
enabled: false
|
||||
shared: false
|
||||
container_backup:
|
||||
enabled: false
|
||||
shared: false # nocheck: variants-static-override backup shared stays literally true
|
||||
@@ -66,6 +72,9 @@
|
||||
tor:
|
||||
enabled: true
|
||||
- services: # nocheck: variants-auth-isolation
|
||||
litellm:
|
||||
enabled: true
|
||||
shared: true
|
||||
container_backup:
|
||||
enabled: false
|
||||
shared: false # nocheck: variants-static-override backup shared stays literally true
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
- name: "🔐 Verify the gateway contract for {{ application_id }}"
|
||||
ansible.builtin.include_tasks: "{{ lookup('path_absolute', 'roles/svc-ai-litellm/tasks/utils/consumer_contract.yml') }}"
|
||||
@@ -2,6 +2,8 @@ ADMIN_PASSWORD={{ lookup('users', 'administrator').password | dotenv_quote }}
|
||||
|
||||
ADMIN_USERNAME={{ lookup('users', 'administrator').username | dotenv_quote }}
|
||||
|
||||
AI_GATEWAY_BASE_URL={{ LITELLM_OPENAI_BASE_LOCAL_URL | dotenv_quote }}
|
||||
|
||||
APP_BASE_URL={{ lookup('tls', application_id, 'url.base') }}
|
||||
|
||||
BIBER_PASSWORD={{ lookup('users', 'biber').password | dotenv_quote }}
|
||||
@@ -16,6 +18,9 @@ EMAIL_SERVICE_ENABLED={{ lookup('config', application_id, 'services.email.enable
|
||||
# nocheck: playwright-service-gate — gated by the ldap-authenticator addon spec, not the main persona spec
|
||||
LDAP_SERVICE_ENABLED={{ lookup('config', application_id, 'services.ldap.enabled') | string | lower }}
|
||||
|
||||
# nocheck: playwright-service-gate — gated by the ai-llm addon spec, whose addon flag is itself bound to services.litellm.enabled
|
||||
LITELLM_SERVICE_ENABLED={{ lookup('config', application_id, 'services.litellm.enabled') | string | lower }}
|
||||
|
||||
LOGOUT_SERVICE_ENABLED={{ lookup('config', application_id, 'services.logout.enabled') | string | lower }}
|
||||
|
||||
MATOMO_BASE_URL={{ lookup('tls', 'web-app-matomo', 'url.base') | dotenv_quote }}
|
||||
|
||||
Reference in new issue
Block a user