From 5add15915668e5c82454b1f37967de143dc37914 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Thu, 29 Oct 2026 07:35:56 +0100 Subject: [PATCH] test(browser): isolate and await the real zoom extension --- dev/README.md | 2 ++ docs/pre-phase10-ux/DEPENDENCY_AUDIT.md | 11 +++++++++++ docs/pre-phase10-ux/D_ADDENDUM_ACCEPTANCE.md | 2 ++ job-tracker-ui/e2e/browser-zoom-audit.spec.ts | 17 ++++++++++++----- 4 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 docs/pre-phase10-ux/DEPENDENCY_AUDIT.md diff --git a/dev/README.md b/dev/README.md index 7e6d6e2..60a7180 100644 --- a/dev/README.md +++ b/dev/README.md @@ -46,6 +46,8 @@ The same failure pattern is reported in [Docker's Windows issue tracker](https:/ ## Qualified local source activation, 28 October 2026 +29 October recovery addendum: the same stale IPC startup failure recurred after Desktop had stopped. Exact inspection again found only zero-byte runtime sockets. Failed local Desktop processes were stopped and the bounded socket directories were moved to `Docker/run.stale-20261029-0622`, `Docker/run.stale-20261029-0624` and `docker-secrets-engine.stale-20261029-0624` under the same local AppData parent. No Docker data/settings/volume/model directories were moved. The engine and existing local development services recovered; a fresh isolated MariaDB lane then passed 90 tests with cleanup exit zero. This does not change the source activation identity below. + The existing development stack now runs source checkpoint `3f4db8ca8183704945915ae4d2053e641bafa5b2`. Local URL: **http://jobbjakt.local**. The frontend deliberately rejects an unmatched `localhost` Host header; the canonical host was preserved. Mailpit remains at `http://localhost:8026`. Backend image: `sha256:462b1d14fe871d64b16a2db28fda30a433657dd4d8ed5f61587a6b80685fb0cf`. Frontend image: `sha256:a59b8313233a20f6a2f0a8cc23785d224b58678e5758c3985fa55f238402886f`. All qualified source-file hashes were rechecked before activation. The backend is the same immutable image used by the passing Linux qualification. The frontend was rebuilt from that source with `/api` routing and the existing local nginx configuration. The qualified static test export was archived separately before this build. diff --git a/docs/pre-phase10-ux/DEPENDENCY_AUDIT.md b/docs/pre-phase10-ux/DEPENDENCY_AUDIT.md new file mode 100644 index 0000000..d82f0b2 --- /dev/null +++ b/docs/pre-phase10-ux/DEPENDENCY_AUDIT.md @@ -0,0 +1,11 @@ +# Dependency advisory addendum + +29 October 2026. Evidence is point-in-time and separate from the sealed source-diff scan. + +The solution-wide transitive NuGet audit completed using the installed .NET 9 SDK. It reports one distinct moderate advisory, repeated for the API and its unit-test consumer: inherited `MySqlConnector` 2.4.0, [GHSA-473q-m89c-ghf8](https://github.com/advisories/GHSA-473q-m89c-ghf8). The advisory affects versions before 2.6.1 when `VerifyCA`/`VerifyFull` is used without a configured CA and authentication can send a password before certificate verification. Its stated workarounds include a configured CA or a local connection. This is an existing dependency, not introduced by the A/B/C/D changes. + +Evidence: `.artifacts/pre-phase10-ux/integration-nuget-audit-02.json`. The first invocation used an unsupported SDK option and performed no audit; that retained attempt is `integration-nuget-audit-01.json`. Neither file is relabelled as a clean dependency result. + +The isolated MariaDB lane explicitly uses loopback with `SslMode=None`; it does not test this TLS mode or establish deployed TLS safety. Production configuration and services were not inspected or changed. Required release-owner action: establish whether deployed connection/authentication settings meet the advisory preconditions, then qualify an upgraded connector or enforce an appropriate trusted CA before production acceptance. Any driver change must independently rerun real MariaDB migration, transactions and RD21 tests; it is not bundled into a running frozen UX regression. + +Other recorded audits: production npm has zero known advisories; full npm retains five low-severity development-tool entries. The 48-dependency Python import lock has zero reported known vulnerabilities. Source and artifacts for these checks are in `.artifacts/pre-phase10-ux/integration-independent-01/`. These results do not prove absence of unknown vulnerabilities, qualify parser fuzzing or close production infrastructure/model/cache debt. Security/privacy remains CONDITIONAL and production NO-GO. diff --git a/docs/pre-phase10-ux/D_ADDENDUM_ACCEPTANCE.md b/docs/pre-phase10-ux/D_ADDENDUM_ACCEPTANCE.md index c1774e8..fea2287 100644 --- a/docs/pre-phase10-ux/D_ADDENDUM_ACCEPTANCE.md +++ b/docs/pre-phase10-ux/D_ADDENDUM_ACCEPTANCE.md @@ -30,6 +30,8 @@ The initial zoom recovery run passed both admin cases but was stopped while wait ## Scope and gates +Later integration replay found a remaining zoom-extension lifecycle race after the first four-case pass. The original `integration-03/` attempt was stopped and remains incomplete; its runtime hashes stayed unchanged and its test-source change is explicit. A direct trace records the worker closing before evaluation. The final test harness verifies the generated worker has executed before installing its network guard, permits only that exact local worker URL in addition to its original local routes, and uses short owned temporary paths with cleanup. `zoom-lifecycle-04.json` records all six repeated EN/light and NB/dark real-zoom cases passing with retries disabled. The interrupted `zoom-lifecycle-03` attempt remains partial. No application or geometry assertion changed. + The sealed security scan through `d52403d` does not include this later delta. Its bounded filters, rate policy, failure-event query and UI were reviewed separately; no new security blocker was identified. This is source review, not a new sealed scan or production acceptance. Full cumulative ordinary/specialized browser, final Docling, PDF fidelity, source/runtime closure and local C/D activation remain pending. Historical PDF coordinate-oracle failures, human/device/language/print checks, real AI extraction qualification and production infrastructure debt remain open. No production operation, main merge, deployment or Phase 10 cleanup occurred. diff --git a/job-tracker-ui/e2e/browser-zoom-audit.spec.ts b/job-tracker-ui/e2e/browser-zoom-audit.spec.ts index f47359d..5b10fd5 100644 --- a/job-tracker-ui/e2e/browser-zoom-audit.spec.ts +++ b/job-tracker-ui/e2e/browser-zoom-audit.spec.ts @@ -1,5 +1,7 @@ import { expect, test, type APIRequestContext, type Page } from '@playwright/test'; import fs from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; import { translations } from '../src/i18n/translations'; const apiUrl = 'http://localhost:5302/api'; @@ -48,7 +50,9 @@ async function geometry(page: Page) { for (const [language, mode] of [['en', 'light'], ['nb', 'dark']] as const) { test(`[P5-15-02 E08 C06] actual browser zoom ${language}/${mode}: collect 100/200/400 percent reflow without changing document geometry`, async ({ playwright }, info) => { test.setTimeout(240_000); - const extension = info.outputPath('local-zoom-extension'); + // Keep Chromium's runtime paths short in deep worktrees and repeat-run output folders. + const runtime = await fs.mkdtemp(path.join(os.tmpdir(), 'jt-zoom-')); + const extension = path.join(runtime, 'extension'); await fs.mkdir(extension, { recursive: true }); await fs.writeFile(`${extension}/manifest.json`, JSON.stringify({ manifest_version: 3, name: 'Local audit browser zoom', version: '1.0', @@ -56,21 +60,23 @@ for (const [language, mode] of [['en', 'light'], ['nb', 'dark']] as const) { background: { service_worker: 'background.js' }, })); await fs.writeFile(`${extension}/background.js`, 'globalThis.auditZoomReady = true;'); - const context = await playwright.chromium.launchPersistentContext(info.outputPath('local-browser-profile'), { + const context = await playwright.chromium.launchPersistentContext(path.join(runtime, 'profile'), { channel: 'chromium', headless: true, viewport: null, deviceScaleFactor: undefined, // The bundled browser otherwise disables the extension that owns real tab zoom. ignoreDefaultArgs: ['--disable-extensions'], args: ['--window-size=1280,900', `--disable-extensions-except=${extension}`, `--load-extension=${extension}`], - }); + }).catch(async error => { await fs.rm(runtime, { recursive: true, force: true }); throw error; }); const admin = await playwright.request.newContext(); let adminHeaders: Record | undefined; let createdAdminId: string | undefined; const records: unknown[] = []; try { const worker = context.serviceWorkers()[0] || await context.waitForEvent('serviceworker'); + expect(await worker.evaluate(() => (globalThis as unknown as { auditZoomReady: boolean }).auditZoomReady)).toBe(true); await context.route('**/*', route => { const url = new URL(route.request().url()); - return ['localhost', '127.0.0.1'].includes(url.hostname) || ['data:', 'blob:', 'about:'].includes(url.protocol) ? route.continue() : route.abort(); + // The generated local extension must finish loading and may restart its own worker. + return url.href === worker.url() || ['localhost', '127.0.0.1'].includes(url.hostname) || ['data:', 'blob:', 'about:'].includes(url.protocol) ? route.continue() : route.abort(); }); await context.addInitScript(({ language, mode }) => { localStorage.setItem('uiLanguage', language); @@ -165,7 +171,8 @@ for (const [language, mode] of [['en', 'light'], ['nb', 'dark']] as const) { } finally { await info.attach('actual-browser-zoom-observations', { body: JSON.stringify({ method: 'chrome.tabs.setZoom; viewport:null; dpr and actual zoom asserted; not human acceptance', browser: context.browser()?.version(), records }, null, 2), contentType: 'application/json' }); await admin.dispose(); - await context.close(); + try { await context.close(); } + finally { await fs.rm(runtime, { recursive: true, force: true }); } } } });