Twenty-five specs called the Playwright APIs with default or hardcoded
timeouts. Routed over Tor those budgets are too small, so a spec fails on
transport latency rather than on the behaviour it asserts.
Route the waits through resolveTimeout and reach pages via gotoOnion, the
helpers the tor branch introduced for exactly this. The remaining changes in
the diff are import wrapping around those calls.
The repository lint enforces this: a raw numeric timeout in a role spec is
rejected with a pointer to resolveTimeout, which is how a hardcoded 500ms
wait added during this session was caught.
Merge work rather than authored here; not deployed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
834 commits from the Tor onion-service branch against 299 of ours, from merge
base 7b646bdece: 127 conflicted files, 3506 files changed.
The two sides are largely orthogonal. Theirs adds svc-net-tor and a `tor` service
across the role tree, plus two sweeping refactors: shipped scripts sorted by
language (files/ruby/, files/python/, files/shell/, scripts/lint/*.sh) and the
unit suite moved under tests/unit/python/. Ours is requirement 036, the native AI
gateway, plus the MCP role work. Most conflicts were both sides appending to the
same service map, resolved as unions.
Resolutions that were not unions:
Credentials moved namespace. Theirs renamed meta/schema.yml to meta/secrets.yml
and re-addressed credentials as secrets.credentials.<name>. Git carried our
litellm_api_key blocks through the rename, but every consumer we added still read
'credentials.<name>', which no longer resolves. All 45 call sites are migrated.
plugins/filter/container_volumes.py is deleted by theirs and relocated to
plugins/lookup/container_volumes.py, where it carries the same tmpfs defect our
f85996b43 had just fixed: a hard-coded {"size": ...} that silently drops a per
mount `mode`. The fix is re-applied in the relocated file, as that commit's merge
note anticipated.
compose.yml keeps only INFINITO_PLAYWRIGHT_REPORTS_BASE_DIR. Theirs also forwards
INFINITO_PLAYWRIGHT_STAGE_BASE_DIR; that line is deliberately dropped, per the
analysis recorded in 45804e9ad -- STAGE has a drift-proof role-vars fallback,
while forwarding it promotes a setdefault-pinnable .env value above that SPOT.
tasks/stages/01_constructor.yml takes their include, ./tasks/utils/setup/group.yml,
because their refactor removed tasks/groups/*-roles.yml, leaving our include
target nonexistent. Our task name is kept, since svc-virt is in the loop.
Makefile swarm-zombie is a union: their validate plus teardown, then our
lab_subnet cleanup, which their side dropped while leaving lab_subnet.sh as the
only orphaned reference in the file.
01_bootstrap.sh keeps our deterministic --ip pinning, which distinguishes an
already-attached node from a real failure, over their `|| true`.
Their language-sorted layout orphaned references our side still held: zammad's
two scripts/apply/*.rb, jenkins' java-with-ca-trust.sh, and the notify topics
their handlers renamed to hyphenated form. All corrected.
roles/sys-svc-compose/files/shell/swarm/stack_ready.sh gains our latched-update
block: paused, rollback_paused, rollback_started and rollback_completed exit 2
rather than polling a state that cannot resolve without another deploy.
config_from_run is unioned to (title, logged, jobs); the production caller in
trigger/__main__.py now passes jobs, without which our suite suppression would
have been inert.
READMEs are regenerated rather than hand-merged: 15 LiteLLM rows and 91 Tor rows
now derive from the merged metadata, which is itself evidence the meta merge is
coherent.
Verified: 5161 unit tests pass. The single failure, test_query.py TestSortSpec,
also fails on fork/feature/svc-net-tor at b51d2f9919, measured in a baseline
worktree -- so this merge introduces no new unit-test failure. Two modules are
excluded from that run because `baudolo`, a dependency their branch introduces,
is not installed on this host.
Not verified: no deploy has run against the merged tree, and make quality has not
been run end to end here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Discourse AI is configured through an LlmModel row rather than settings, so the
addon upserts one via rails: provider open_ai, the gateway's chat-completions
URL, the model the gateway serves, and this role's virtual key. It then binds
that row to ai_default_llm_model and to the bot, because a model that exists
but is bound to nothing serves no surface.
llm_models.url is POSTed verbatim, so DISCOURSE_LITELLM_CHAT_ENDPOINT carries
the full /v1/chat/completions path rather than a base URL. Every other consumer
in this requirement takes a base and appends; getting this one wrong produces a
404 at prompt time, long after a green deploy.
Three things had to be cleared before any of that could work.
Discourse refuses to call a private address unless the host is in
allowed_internal_hosts, and it fails as an opaque provider error rather than as
a blocked request. The script appends the gateway host to that list and leaves
existing entries alone, which matters because the OIDC bootstrap in
config.yml.j2 appends the discovery host to the same pipe-separated setting.
Both sides check membership before adding, so they compose instead of
clobbering each other.
The launcher-managed container sits outside the compose graph and does not join
the gateway network on its own. handlers/stack_host.yml adds
DISCOURSE_LITELLM_NETWORK to its network list and config.yml.j2 passes the
matching --network flag, both only while litellm is enabled.
The bootstrap aborted before the plugin could install at all. after_code cloned
every enabled plugin unconditionally, but a bundled plugin already sits in
$home/plugins, git exits non-zero on the clone, and the whole bootstrap stops.
discourse-ai is bundled in current images, so it now skips plugins whose source
is bundled.
mem_limit goes from 2g to 4g. `rails r` boots a second Rails inside the
container already running unicorn and sidekiq, and at 2g the kernel kills it
mid-run: measured in deploy-036-11 and again in deploy-ai-v2-7, where the task
died with "Killed" and the deploy ended at exit 137. Upstream documents 4 GB as
Discourse's own minimum, so this corrects an under-provisioned limit rather than
working around the task. The variant resource budget still passes.
Verified: the OOM with the exact task and exit code across two independent runs,
the bundled-plugin abort, and that the budget test holds at 4g.
Not verified: the registration has never completed, because every attempt so far
died at the OOM first. The eight-fact read-back assertion and
addons/discourse-ai.spec.js are both entirely unexercised, the spec because the
addon bridge gate had it skipping.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Over Tor the redirect chain behind a login/logout/OIDC click routinely exceeds Playwright's fixed 30s action timeout, so the click throws before the flow's scaled waits run. Sweep all role Playwright suites and the shared persona helpers: give every navigation/auth-transition click an onion-scaled timeout (.click({ timeout: resolveTimeout(30_000) })) and route the remaining raw millisecond literals (timeout options, waitForTimeout, sleep-promise setTimeout, positional wait args) through resolveTimeout. Local UI clicks (menus, toggles, modal dismissals, DOM clicks inside evaluate) stay untouched.
Two new lints keep it that way: test_no_raw_timeout_literals forbids raw numeric timeout literals in Playwright specs (opt-out: nocheck: raw-timeout), test_persona_clicks_scaled requires every click in the shared persona auth flows to carry a resolveTimeout timeout (opt-out: nocheck: unscaled-click).
Verified: node --check on all 133 changed JS files, all 13 playwright lints green, make autoformat clean, make test green (2745+196+163+4 tests OK).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New lint test_no_raw_goto.py forbids raw `page.goto(...)` in role playwright specs + persona flows (the gotoOnion wrapper in personas/utils is exempt) so navigation retries transient Tor-transport errors over onion. Migrates every raw call across ~75 roles (343 sites) to `gotoOnion(page, ...)` and teaches gotoOnion to detect relative onion URLs (`isOnionCanonical`). gotoOnion is a behaviour-identical drop-in on clearnet (single attempt, same return). Verified: no-raw-goto lint green, eslint 0 errors, make test green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the global TIMEOUT_FACTOR and a shared timeouts.js helper: resolveTimeout(base) scales by TIMEOUT_FACTOR and, over a .onion target, an extra multiplier, so Tor's per-request latency no longer trips hard-coded timeouts. Migrate every raw timeout in the specs (timeout:/timeout=/setTimeout/waitForTimeout) to resolveTimeout, stage the helper in all three staging paths, and add test_no_raw_timeouts to forbid raw numeric timeouts. Clearnet behaviour is unchanged (factor 1: resolveTimeout(N) === N).
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>
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>
Addon specs assert the integration is genuinely wired (admin config holds the partner value, host distinct from the app), not just that a page loads. Robust to the live DOM: strict-mode-safe locators (.first()), enabled-signal from the app's own panel (not the lazy apps list), and test.skip (not fail) when an app is disabled (NC-version-incompatible) or its partner role is absent/unconfigured.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add tests/lint/ansible/services/test_addons_playwright_spec.py requiring roles/<role>/files/playwright/addons/<id>.spec.js for EVERY addon of EVERY role — no mechanism auto-exempt and no # nocheck escape. Ship the 87 per-addon specs (gated via skipUnlessAddonEnabled so they skip cleanly when the addon is disabled in the current variant), the test-e2e-playwright addon-gating harness (addon-gating.js, playwright.config.js, recursive 02_run_one.yml), the shared odoo helper, and the ADDON_<ID>_ENABLED flags in the six playwright.env.j2 templates. Relax test_env_file_schema to skip Jinja-templated keys in the ADDON loop. Doc updates (suppression.md, requirements/026) follow in a separate commit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>