`bond` is a 0..1 factor for how strongly a service is tied to the overall
integration: bond: 1 = always required, bond: 0 = omittable. Groundwork for
tooling that later scores service importance and decides what can be dropped.
Named `bond` (not `bound`) to avoid clashing with the resource bounds/limits.
Add test_service_bond.py (lint): every enabled + shared service must declare a
valid `bond` (number in 0..1). Seed `bond: 1` as a placeholder on all 638
enabled+shared service entries across 88 roles, to be refined later.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend test_compose_resource_limits.py to enforce resource limits on every
container a role runs itself (not provided by another role via the service
registry): the primary entity needs min_storage/cpus/mem_reservation/mem_limit/
pids_limit; uncovered sidecar containers need the four resource keys. A service
is exempt only when another role covers it (e.g. discourse's postgres ->
svc-db-postgres, whose entity declares the limits), replacing the previous
blanket shared:True skip.
Declare the previously-missing limits across 18 roles: mostly self-run redis
cache sidecars (cpus "0.5" / 128m / 512m / 512), plus the minio mc client,
the coturn gateway, and the web-svc-* css/html/asset/cdn/file provider entities.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mbake line-wrap of the long configvalue jinja expression. Cosmetic only;
the rendered value is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Leftover 'if (popup) await popup.close()' referenced a variable removed
together with the connect-button/popup OAuth flow -> eslint no-undef ->
lint-javascript red -> make test red. make test is green again. No change
to the spec's assertions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
OpenProject's integration_openproject renders no 'Connect to OpenProject'
button on connected-accounts (unlike discourse/gitlab/mattermost); the
connection happens via the Files smart-picker / the OpenProject side. The
spec's connect-button + OAuth-authorize popup flow tested non-existent UI.
Assert the real provisioned bidirectional coupling instead, read from the
admin initial-state config: openproject_instance_url (partner host),
openproject_client_id, openproject_client_secret, authorization_method=oauth2.
NOTE: NOT yet verified green live -- the compose-playwright verify run was
OOM-killed (exit 137) before finishing. High confidence by logic only: in the
prior run this spec already passed the instance-URL check (same JSON.parse
path), failing only on the now-removed connect-button assertion, and all
asserted fields are confirmed present in the decoded admin config. Re-run a
scoped verify to confirm before relying on it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
integration_gitlab/integration_mattermost read client_id/client_secret via
crypto->decrypt(appConfig->getValueString(lazy:true)). Provisioning set them
with 'config:app:set --sensitive', the wrong scheme -> "Authenticated
ciphertext could not be decoded" -> the connected-accounts admin page 500s,
cascading onto the discourse/matrix/mattermost addon specs.
Add set_encrypted_appconfig.php (crypto->encrypt + appConfig setValueString
lazy -- the exact scheme the apps' setEncryptedAppValue uses) and route both
provisioning hooks through it. Verified live: gitlab client_id is now an
encrypted blob and connected-accounts renders again.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- discourse-activity-pub, integration_matrix specs: drop unused catch
binding and useless json init (eslint no-unused-vars/no-useless-assignment)
- openwebui playwright.env.j2: order OLLAMA_SERVICE_ENABLED after
OIDC_ISSUER_URL (env keys must be ASC, test_alphabetical_sort)
- test_addon_env_flags.py: autoformat
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The drawio/files_bpm/external/fileslibreofficeedit/whiteboard addon specs
asserted guessed routes/selectors that do not exist on the live stack.
Verified against the running Nextcloud (make compose-playwright):
- drawio, fileslibreofficeedit: no standalone app route -> assert the app
is registered+enabled in OC.appswebroots (Files file-action apps)
- files_bpm: modeler surface is #bpmn-app, not #canvas.bpmn-canvas
- external: admin section is #external + #add_external_site
- whiteboard: backend URL is injected in #initial-state-whiteboard-url
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- svc-ai-ollama variant-0: preload smollm2:135m (smallest instruct model for CI)
- integration_openai meta: Service URL -> {base}/api for the OpenWebUI backend
- web-app-openwebui: enable_api_keys config flag (services.yml + vars) rendered into
ENABLE_API_KEYS so sk- API keys authenticate against the OpenAI-compatible API
- integration_openai spec: drop the DOM-gated skip, assert the partner backend host
(openwebui chat.ai / flowise flow.ai), distinct from Nextcloud
- openwebui: OLLAMA_SERVICE_ENABLED playwright flag + test-ollama-chat.js asserting
the preloaded model answers a chat completion over OpenWebUI -> Ollama
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the settings-page check with the real bridge: the wp-discourse connection
tab carries the partner Discourse base URL (distinct host), and the provisioned
Discourse API credentials authenticate against the partner instance
(/session/current.json resolves the publishing user on the Discourse host).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the users-list-renders check with the real cross-partner bridge: wp-login
redirects to the Keycloak partner authorize endpoint (distinct host) with
response_type=code, the provisioned client_id and a scope carrying the groups claim
the RBAC mapper consumes, then lands on the wp-admin role-mapping surface.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the wp-admin settings-page check with a real federation probe: WebFinger
resolves the author acct to a rel=self ActivityStreams actor, which is fetched and
asserted to carry the AS @context, an actor type, id, inbox/outbox on this host, and
an RSA publicKey — the surface remote Fediverse partners federate with.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the generic surface check with the Inventory app identity, no invalid-action
error, the stock.picking operations dashboard, and stock-specific operation types
(Receipts/Deliveries/Internal Transfers).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the generic surface check with the Sales app identity (breadcrumb/title),
the sale.order quotations list/kanban action, and the New create button.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the generic surface check with no-error-page + the CRM control panel and a
CRM-specific surface (lead/opportunity kanban, New, Pipeline) plus CRM domain
vocabulary, proving the crm module loaded.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the generic web-client surface check with module-specific signals: no Odoo
error dialog, the Accounting/Invoicing app brand in the navbar, and an
account-specific top menu (Customers/Vendors/Chart of Accounts).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the DOM-gated skip; assert the xwiki admin instances table renders (route not
404), no 'no wikis registered' placeholder, and a configured instance-url input
whose host is the XWiki partner, not Nextcloud.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the generic Files-app check with the app's own settings/admin/whiteboard
section and assert the collab-backend URL field is present and non-blank, proving
the plugin_configuration reached the running app.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keep the deterministic config coupling (OCS spreed signaling capability + the Talk
admin TURN server pinned to the web-svc-coturn partner host, STUN server, signaling
host) with a best-effort live 'Test this server' check; drop the comment blocks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Assert the admin section renders, the wopi_url field carries a real partner URL
whose host is distinct from Nextcloud, and the Collabora connection-failure banner
stays absent, proving the connector reached the partner WOPI server.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Assert the recognize admin Vue component (#recognize) and its own Face-recognition
section render, not the generic settings shell.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the config-presence check with a real round trip: upload a valid .docx,
open it, and assert the ONLYOFFICE editor iframe is served by the web-svc-onlyoffice
partner host, shows no JWT/download error, and renders its editor surface.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Assert the metadata app is registered in OC.appswebroots and its own frontend
bundle (apps/metadata/...) is injected into the Files page, proving the provider is
loaded and coupled, not just listed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the best-effort early-return; assert the admin instance URL is the partner
(not Nextcloud) and the per-user connect drives the partner /oauth/authorize with
the provisioned client_id + response_type=code.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the DOM-gated skip; assert the admin panel exposes the provisioned partner
instances list (distinct from Nextcloud) and the per-user connect drives the OAuth
authorize on the partner instance with the provisioned client_id + response_type=code.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Assert the groupfolders admin Vue app (#groupfolders-wrapper/#groupfolders-root)
mounts, not the generic settings shell, proving the app is installed and enabled.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Target the app's own settings/admin/fileslibreofficeedit route (not 404) and assert
its LibreOffice editor admin panel and document-editor server URL field render.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Open the Files New menu and assert the 'New link (.URL)' and 'New link (.webloc)'
entries the app injects are present, proving its frontend bundle loaded.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Target the app's own apps/files_bpm/ route (not the generic Files app) and assert
it resolves (not 404) and the #canvas.bpmn-canvas modeler surface renders.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Target the app's own settings/admin/external section and assert its description
text and 'New site' action render, proving the external app's section and template
are served by the enabled app (not the generic additional-settings shell).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The app is backend-only (no user route); assert via the Provisioning OCS registry
that event_update_notification is in the enabled-apps list and not in the disabled
list, so a disabled/absent app fails.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the DOM-gated skips; assert the settings/user/epubviewer section resolves
(not 404), the #reader-personal panel renders, and the #EpubEnable checkbox
reflects the persisted user config.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the DOM-gated skips; assert the apps/drawio/ route resolves (not 404) and the
drawio-specific editor surface mounts, so a disabled/broken app fails.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Navigate to apps/bbb/ and assert the cloud_bbb app shell + its own room-management
surface render, in addition to the admin api.url/api.secret partner coupling.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the skip stub; resolve the @whatsappbot appservice profile against the partner
Synapse host and assert it returns 200 with a WhatsApp-branded display name.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the skip stub; assert the @telegrambot appservice profile resolves on Synapse
(200, not 404/5xx) and returns the bridge bot profile object.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the skip stub; assert the @slackbot appservice profile resolves on Synapse
(200, not 404/5xx) with a Slack-branded display name.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the skip stub; assert the @signalbot appservice profile resolves on Synapse
with a Signal-branded display name, plus a negative control (unregistered bot 404).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the skip stub; probe Synapse for the @instagrambot appservice bot (must
resolve, not 404/5xx) and add a negative control (an un-bridged localpart must 404)
so the probe really exercises the bridge appservice namespace.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the unconditional skip stub; probe the partner Synapse homeserver for the
@facebookbot appservice bot profile and assert it resolves (HTTP 200), proving the
bridge registered its appservice and the round-trip to Synapse succeeded.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the DOM-gated skip; assert docker_manager is present and enabled in
/admin/plugins.json and the /admin/upgrade.json surface it owns is served.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the existence-only checks; assert ldap_auth/sync enabled, ldap_sync_host is a
distinct partner host (not Discourse itself), a numeric bind port, ldap_base_dn is a
real directory root, and ldap_bind_dn is rooted in the same partner tree.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the DOM-gated skip; assert akismet_enabled is active, anti_spam_service is
akismet, and akismet_api_key is provisioned so Discourse can authenticate against
the Akismet partner API.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the DOM-gated skip; assert activity_pub_enabled is active, the WebFinger
actor-discovery endpoint is live, the /ap federation surface serves JSON, and
remote-handle validation resolves a fediverse partner host distinct from the
Discourse host (real cross-host federation, not a local echo).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Push the OpenProject OAuth credentials (authorization_method, openproject_client_id,
openproject_client_secret) into Nextcloud before the NC OAuth2 client step so the
forward connect is always provisioned. The reverse-direction Nextcloud OAuth2 client
relies on occ oauth2:add-client, which Nextcloud 33 does not ship, so it is now
failed_when: false with its dependents in a guarded block — the deploy completes
instead of aborting before the Playwright suite runs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The integration_openproject / integration_gitlab provisioning and the user_ldap
config probe read their state with tasks that always run (no when:) and
failed_when: false, so their .stdout is always defined; 'X.stdout | default('') |
trim' is identical to 'X.stdout | trim'. Remove the dead default('').
Genuinely-guarding defaults are kept: the s01 config-id fallback, the skippable
ldap auto-create stdout/stderr, the regex_search None guards, and the outer
default(..., true) fresh-or-persisted secret fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
OpenProject only returns a fresh OAuth client secret when the application is first
created; on a re-deploy where the app already exists but Nextcloud never persisted
the secret (e.g. an earlier deploy failed downstream at the storage step), the
secret is unrecoverable and the provisioning assert tripped, aborting the play
before the Playwright suite ran.
ensure_nextcloud_storage.rb now destroys and recreates the OAuth application when
it exists but Nextcloud holds no secret (gated to the first call via the absent NC
client id), so a fresh secret is always emitted. The provision task passes the
persisted-secret read as NC_HAS_OP_SECRET only on that first call so the second
(client-register) call never destroys the freshly created app. Verified
in-container: the recovery path emits a fresh openproject_client_secret.
Also drops the redundant | default('') on the always-set
nextcloud_op_client_secret_current.stdout in this file.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drive the real bridge: pin the per-user #moodle-url to the deployed partner
Moodle host (distinct from Nextcloud), persist it, then Connect and assert the
POST /apps/integration_moodle/get-token response is a genuine partner verdict
(200 with user_name minted by the partner webservice, or 401 partner-refused) —
proving the request reached the partner Moodle, not a local stub. bridges:
[moodle] means the deployment gate skips it when the partner is not deployed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Assert the admin oauth_instance_url points at the partner Mattermost host (not
Nextcloud) and the admin panel exposes the provisioned OAuth client, and the
per-user connect drives an OAuth authorize redirect to that partner host with
the provisioned client_id + response_type=code. Backed by the
integration_mattermost_provision hook; bridges: [mattermost] with the partner in
the deploy set means this runs live and fails if provisioning or the connect is
broken.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the DOM-gated skip; assert the admin Matrix panel renders, the configured
homeserver is the deployed partner (distinct from Nextcloud), and the per-user
connect drives a real session against the partner homeserver (a browser
/_matrix/client/ call to the partner, or the nextcloud integration_matrix
connect round-trip). bridges: [matrix] with the partner in the deploy set means
this runs live and fails on a broken coupling instead of skipping on the DOM.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>