SSL fix (ADR-0009 Finding 7):
- Replace ssl.create_default_context() with system_ssl_context(capath)
in client_registry.py, async_network.py, notifications/providers.py,
git/_base.py. Previous fix (0.5.1) only covered LLM clients; the
Superset API client still used ssl.create_default_context() which
loads cafile (flat bundle) where OpenSSL 3.x ignores intermediate CA
certificates. system_ssl_context() uses capath only (hash symlinks).
Agent fix:
- Extract _check_llm_provider_health + _llm_status from agent/app.py
into agent/_llm_health.py. The /api/agent/llm-status endpoint was
importing from agent/app.py which triggers 'import gradio' at module
level. Backend container does not have gradio installed, causing
ModuleNotFoundError (500 error) every 30s on health check polling.
Config:
- Add ALLOWED_ORIGINS to docker-compose.yml + docker-compose.enterprise-clean.yml
ADR-0009 updated: Layer 2 table expanded with 4 missing clients,
Finding 5 reconciled (LLM_CA_CERT_URLS restored in 0.5.1), version 0.5.2.
Verified: 1110 unit tests passed, gradio import isolation confirmed.
Backend entrypoint sources certs.sh for CA certificate installation,
but the file was never copied into the Docker image — causing
container crash loop on startup (regression in 8fd23f7e).
Changes:
- docker/backend.Dockerfile, docker/all-in-one.Dockerfile: COPY certs.sh
- docker/backend.entrypoint.sh: remove dead install_llm_ca_certs,
install_certificates (replaced by docker/certs.sh); update @INVARIANT
- backend/src/core/ssl.py: fix describe_context() to report actual
system cert count (SSLContext.capath attr does not exist in Python 3)
- __tests__: rewrite stale tests asserting LLM_SSL_VERIFY=false →
verify=False; behaviour is permanently removed — always CERT_REQUIRED
- backend/tests/integration/test_backend_container.py [new]: 5 tests
verifying certs.sh presence, sourceability, and full-stack smoke
(testcontainers PG → entrypoint → migrations → health)
- conftest.py: restore health-wait loop and fixtures in superset_container
- ADR-0009, README.md, scripts, .env: align docs with centralized SSL
Production SSL failure for lite.ai.rusal.com confirmed: LLM_CA_CERT_URLS
not set → LLM CA certs not downloaded. System CA store has corporate
certs from /opt/certs but lite.ai.rusal.com uses a different CA.
Diagnostic script scripts/diag_container.py covers all 6 check categories
per ADR-0009: env, system CA, openssl capath/cafile, Python SSLContext,
httpx connectivity, encryption health.
Fix: set LLM_CA_CERT_URLS in .env.enterprise-clean or place the CA .crt
in ./certs/ on the host. Run diag_container.py to verify.
Modal now mounts at document.body level via svelte mount/unmount.
Extracted WizardContent.svelte for portal rendering.
Eliminates 32px margin from page layout wrapper
(max-w-7xl/space-y-6/px-4 container).
Added KeyRecoveryWizard as portal wrapper, WizardContent as
the actual modal component.
Critical (C1): Added has_permission('security', 'READ') to /health and
/fingerprint endpoints; has_permission('security', 'WRITE') to /recover.
Previously any authenticated user could enumerate encrypted secrets and
overwrite stored values.
High (Bug #1): Fixed recover endpoint — updated/failed status now correctly
reflects whether a non-empty replacement value was submitted. Empty values
now report 'skipped' instead of falsely 'updated'.
High (Bug #2, #3): Replaced all hardcoded English strings in
KeyRecoveryWizard.svelte and SystemSettings.svelte with $t.settings.*
i18n lookups. Keys already existed in en/ru settings.json.
High (H1): Added _sanitize_error() helper to truncate + scrub exception
messages before logging, preventing potential secret leakage in log output.
Cleanup: Moved LLMProviderConfig import to module top. Instantiate
ConnectionService once before for-loop. Added @TEST_EDGE declarations
and @PRE/@SIDE_EFFECT to C4 contract.
Tests: 226 passed
Canonical variables:
- AUTH_SECRET_KEY — JWT signing key for backend + agent (was split across
AUTH_SECRET_KEY / JWT_SECRET)
- SERVICE_JWT — agent→backend service token
- No JWT_SECRET fallback: decoder fails with migration guidance if only
JWT_SECRET is set
Compose files unified:
- docker-compose.yml, docker-compose.enterprise-clean.yml,
docker-compose.e2e.yml — all use AUTH_SECRET_KEY
- build.sh generated compose now passes AUTH_SECRET_KEY + ENCRYPTION_KEY
to backend
Env examples unified and completed:
- .env.example — comprehensive template, all compose vars
- .env.enterprise-clean.example — production template
- backend/.env.example — backend-only run
- docker/.env.agent.example — agent-only run
- NEW: .env.current.example, .env.master.example,
.env.e2e.example, frontend/.env.example
Tests aligned:
- conftest sets AUTH_SECRET_KEY (canonical value matched across test files)
- test mocks use canonical name
- 1176 passed, 0 failed
Replaced src.core.auth.jwt.decode_token import with local _jwt_decoder.py:
- Tries JWT_SECTRET first, falls back to AUTH_SECTRET_KEY (avoids key mismatch
when both env vars are set in different test files)
- verify_aud disabled — backend tokens have audience; agent ignores
- No auth DB dependency — removed AUTH_DATABASE_URL requirement from agent
Cleanup:
- Removed src/core/auth/ from agent Dockerfile COPY
- Removed AUTH_SECTRET_KEY, AUTH_DATABASE_URL from agent compose env
- conftest sets AUTH_SECTRET_KEY for test consistency
- Updated test patches to new import path
Tests: 1176 passed, 0 failed
- Added python-jose[cryptography] to requirements-agent.txt
- Made sqlalchemy.orm.Session and TokenBlacklist imports lazy in jwt.py
so decode_token works without pulling ORM models into agent image
- Added src/core/auth/__init__.py, config.py, jwt.py to agent Dockerfile COPY
- Added AUTH_SECRET_KEY env var to agent compose (reads from JWT_SECRET)
- Quick status pills (Упавшие/В работе/Успешные) always visible
- Search input with flex spacer in same row
- Explicit "Фильтры" disclosure button with filter icon, badge count, chevron
- aria-expanded / aria-controls for accessibility
- Expandable panel: type, status, time range, sort controls
- Remove bottom status bar ("Показано X из Y", "Активно фильтров")
- Update tests for expanded state and quick pill interactions
- Replace raw <button> with <Button variant="ghost">
- Dim zero-count badges with opacity-50
- Add transition-colors duration-300 to count numbers
- Replace inline reconnect link with themed <Button>
- Add min-w-0 overflow-hidden to TaskList shell container
- Add filtered_empty UX state to contract
- Update layout contract test to verify TaskList source directly
- Add transition-colors to status badges
{model.targetLanguages} is invalid shorthand — Svelte only supports
bare identifiers, not property access. Changed to explicit
targetLanguages={model.targetLanguages}