From b8aee7fb37313072a37521a0f0cf1b22ac104c05 Mon Sep 17 00:00:00 2001 From: "lusendong.6789" Date: Tue, 10 Nov 2026 00:45:23 +0800 Subject: [PATCH] fix(smoke): qualify M2 rendering before coverage diagnostics Signed-off-by: lusendong.6789 Co-authored-by: TRAE CLI --- .../rfcs/goal-workspace-surface-v0.md | 11 +++++ .../rfcs/goal-workspace-surface-v0.zh-CN.md | 7 +++ examples/personal-workspace-browser-smoke.mjs | 1 + .../goal-workspace-m2.mjs | 38 ++++++++++++---- .../goal-workspace-m2.test.mjs | 45 ++++++++++++++++++- 5 files changed, 92 insertions(+), 10 deletions(-) diff --git a/docs/architecture/rfcs/goal-workspace-surface-v0.md b/docs/architecture/rfcs/goal-workspace-surface-v0.md index 4ab1290832..d475a38c47 100644 --- a/docs/architecture/rfcs/goal-workspace-surface-v0.md +++ b/docs/architecture/rfcs/goal-workspace-surface-v0.md @@ -720,6 +720,17 @@ the existing expansion budget. This qualifies the browser driver's target selection; product focus recovery and the causes of earlier partial inventories retain their separate evidence requirements. +Coverage-enabled source runs now qualify the complete M2 journey without V8 +coverage first, enforcing the ordinary below-1-second transition medians, all +15 loaded-lens samples, and the unchanged initial-render and expansion limits. +When coverage is requested, a second complete journey collects coverage and +reports both timing sets while retaining classification, selection, no-effect, +error and remote-source checks. A failed uninstrumented gate stops the run +before coverage can supply a replacement result. Acceptance retains +`renderQualification` and `coverageDiagnostics`; ordinary and packaged runs +execute only the uninstrumented qualification. This changes neither the +workload nor the numerical budgets and does not reclassify earlier failures. + **Checkpoint 2026-11-12 (source-coverage execution).** The source lane now executes the real disposable first-Goal and runtime-readiness backends rather than returning packaged-only PASS receipts. Explicitly required diff --git a/docs/architecture/rfcs/goal-workspace-surface-v0.zh-CN.md b/docs/architecture/rfcs/goal-workspace-surface-v0.zh-CN.md index bb885e90c5..33a7e5d5e8 100644 --- a/docs/architecture/rfcs/goal-workspace-surface-v0.zh-CN.md +++ b/docs/architecture/rfcs/goal-workspace-surface-v0.zh-CN.md @@ -513,6 +513,13 @@ M3、W2/W3、安装态以及 L1/G1 的真实交付和原请求路线返回仍由 全局 Enter 到达前被重渲染替换。验证仍须在既有展开预算内展示全部 200 项。这项证据 验证浏览器驱动的目标选择;产品焦点恢复及早期部分清单的成因仍需各自的证据。 +开启覆盖率时,源码运行现在先在不采集 V8 覆盖率的条件下完成 M2 资格:沿用普通模式下 +每种切换中位数低于 1 秒、全部 15 个切换样本和原有首次渲染/展开上限。随后再执行一次 +完整覆盖率旅程,分类、选择、无副作用、错误及远端来源检查继续阻断,计时作为诊断保留。 +无采集资格失败时,组合运行在覆盖率旅程前结束,覆盖率结果不能替代它。验收结果同时保留 +`renderQualification` 与 `coverageDiagnostics`;普通和打包运行只执行一次无采集资格。 +该流程不改变负载或数值预算,也不重分类此前失败。 + **检查点 2026-11-12(源码 coverage 执行)。** 源码分线现在执行真实的一次性首次 Goal 与运行就绪后端,不再提前返回仅适用于打包 模式的 PASS 回执。明确需要后端的旅程未完成读回时,在 PASS 前失败;其他场景的 diff --git a/examples/personal-workspace-browser-smoke.mjs b/examples/personal-workspace-browser-smoke.mjs index 4f7cd18e04..3bbfe65498 100644 --- a/examples/personal-workspace-browser-smoke.mjs +++ b/examples/personal-workspace-browser-smoke.mjs @@ -219,6 +219,7 @@ async function main() { status: "PASS", note: result.note, ...(scenario.requiresBackendReadback ? {backend_readback_verified: true} : {}), + ...(result.measurements ? {measurements: result.measurements} : {}), duration_ms: Date.now() - startedAt, }; } catch (error) { diff --git a/examples/personal-workspace-browser/goal-workspace-m2.mjs b/examples/personal-workspace-browser/goal-workspace-m2.mjs index d8c1f3de23..ef5b6bc191 100644 --- a/examples/personal-workspace-browser/goal-workspace-m2.mjs +++ b/examples/personal-workspace-browser/goal-workspace-m2.mjs @@ -24,7 +24,27 @@ export function loadedLensBudget(samplesMs, {medianCeilingMs = 1000} = {}) { export const goalWorkspaceM2Scenario = { id: "goal-workspace-m2", - async run({browser, collectCoverage, url}) { + run: context => runM2Rendering(context, runM2Pass), +}; + +// Coverage changes V8 execution. Always qualify the complete original journey +// without it first; an optional second pass retains coverage and diagnostics. +// Neither a fast instrumented pass nor a coverage request can waive the gate. +export async function runM2Rendering(context, runPass) { + const qualification = await runPass({...context, collectCoverage: false}); + const diagnostic = context.collectCoverage + ? await runPass({...context, collectCoverage: true}) + : null; + return { + ...(diagnostic ?? qualification), + measurements: { + renderQualification: qualification.renderBudget, + ...(diagnostic ? {coverageDiagnostics: diagnostic.renderBudget} : {}), + }, + }; +} + +async function runM2Pass({browser, collectCoverage, url}) { const writes = []; let scale = false, omitSelected = false; const many = Array.from({length: 200}, (_, i) => ({todo_id: `scale-${String(i).padStart(3,"0")}`, text: `Synthetic work item ${i + 1}`, title: `Synthetic work item ${i + 1}`, @@ -158,7 +178,7 @@ export const goalWorkspaceM2Scenario = { const renderStart = performance.now(); await now.locator('.goal-owner-group [data-todo-id="scale-199"]').waitFor({state:"attached"}); const initialMs = performance.now() - renderStart; - assert.ok(initialMs < 2000, `200-item Now render exceeded frozen 2s budget: ${initialMs}ms`); + if (!collectCoverage) assert.ok(initialMs < 2000, `200-item Now render exceeded frozen 2s budget: ${initialMs}ms`); assert.equal(await now.locator('.goal-owner-group [data-todo-id]').count(),200); assert.equal(await now.locator('.goal-owner-group [data-todo-id]:visible').count(),15,"Five owners show three tasks each before expansion"); await now.locator('.goal-owner-group[data-owner-id=""] > header').getByText(/1 受阻/).waitFor(); @@ -181,7 +201,7 @@ export const goalWorkspaceM2Scenario = { } assert.equal(await visibleWork(),200,"All work remains reachable in the original owner order"); const expandMs = performance.now() - expandStart; - assert.ok(expandMs < 2000, `Full 200-item expansion exceeded 2s budget: ${expandMs}ms`); + if (!collectCoverage) assert.ok(expandMs < 2000, `Full 200-item expansion exceeded 2s budget: ${expandMs}ms`); await now.getByRole('button',{name:'关系图',exact:true}).click(); await now.locator('.work-map-node').filter({hasText:'Synthetic work item 200'}).waitFor(); await now.getByRole('button',{name:'按负责人',exact:true}).click(); @@ -263,17 +283,17 @@ export const goalWorkspaceM2Scenario = { if (longTaskSupported) await page.evaluate(() => { window.__loopxM2LongTasks.observer.disconnect(); delete window.__loopxM2LongTasks; }); - console.log(JSON.stringify({m2RenderBudget:{initialMs,expandMs,switchMs,switchSamplesMs,...lensBudget, - longTaskSupported,switchLongTasks,nodeCount:200}})); + const renderBudget = {coverageEnabled:collectCoverage,initialMs,expandMs,switchMs,switchSamplesMs,...lensBudget, + longTaskSupported,switchLongTasks,nodeCount:200}; + console.log(JSON.stringify({m2RenderBudget:renderBudget})); assert.equal(writes.length,0,`Rendering created effects: ${writes}`); assert.equal(session.errors.length,0,session.errors.join('\n')); - assert.ok(lensBudget.withinBudget, `Loaded lens switches exceeded the repeated qualification ceiling or 2s page ceiling: ${JSON.stringify({switchSamplesMs,...lensBudget})}`); + if (!collectCoverage) assert.ok(lensBudget.withinBudget, `Loaded lens switches exceeded repeated 1s target or 2s ceiling: ${JSON.stringify({switchSamplesMs,...lensBudget})}`); await verifyRemoteSelection(browser, url); - return {coverageEntries:session.coverageEntries,note:'Responsibility groups, shared task selection/reload, Board/Map switching, no effect requests, desktop/mobile composition. Synthetic status/map; creation is separately qualified against real backend.'}; + return {coverageEntries:session.coverageEntries,renderBudget,note:'Responsibility groups, shared task selection/reload, Board/Map switching, no effect requests, desktop/mobile composition. Synthetic status/map; creation is separately qualified against real backend.'}; } catch(error) {await page.screenshot({path:resolve(outputDir,'goal-workspace-m2-failed.png')});throw error;} finally {await session.close();} - }, -}; +} async function verifyRemoteSelection(browser, url) { diff --git a/examples/personal-workspace-browser/goal-workspace-m2.test.mjs b/examples/personal-workspace-browser/goal-workspace-m2.test.mjs index c9cdce17bd..9f8c763e3b 100644 --- a/examples/personal-workspace-browser/goal-workspace-m2.test.mjs +++ b/examples/personal-workspace-browser/goal-workspace-m2.test.mjs @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import {test} from 'node:test'; -import {loadedLensBudget} from './goal-workspace-m2.mjs'; +import {loadedLensBudget, runM2Rendering} from './goal-workspace-m2.mjs'; const fast = () => Array.from({length:5}, () => [400,500,600]); @@ -58,3 +58,46 @@ test('missing transitions, missing repetitions and invalid timings cannot qualif assert.throws(() => loadedLensBudget(samples)); } }); + +test('coverage retains a separate diagnostic after the required uninstrumented journey', async () => { + const calls = []; + const qualification = {renderBudget:{withinBudget:true}, coverageEntries:[]}; + const diagnostic = {renderBudget:{withinBudget:false}, coverageEntries:['covered']}; + const result = await runM2Rendering({collectCoverage:true, url:'fixture'}, async context => { + calls.push(context); + return context.collectCoverage ? diagnostic : qualification; + }); + assert.deepEqual(calls, [{collectCoverage:false,url:'fixture'}, {collectCoverage:true,url:'fixture'}]); + assert.equal(result.measurements.renderQualification, qualification.renderBudget); + assert.equal(result.measurements.coverageDiagnostics, diagnostic.renderBudget); + assert.deepEqual(result.coverageEntries,['covered']); +}); + +test('a failed uninstrumented gate cannot be rescued by a coverage pass', async () => { + const calls = []; + const failure = new Error('Repeated lens slowdown'); + await assert.rejects(runM2Rendering({collectCoverage:true}, async context => { + calls.push(context.collectCoverage); + throw failure; + }), error => error === failure); + assert.deepEqual(calls,[false]); +}); + +test('coverage correctness failures still fail the combined journey', async () => { + const failure = new Error('Unexpected effect request'); + await assert.rejects(runM2Rendering({collectCoverage:true}, async context => { + if (context.collectCoverage) throw failure; + return {renderBudget:{withinBudget:true},coverageEntries:[]}; + }), error => error === failure); +}); + +test('ordinary qualification runs once and has no coverage diagnostic', async () => { + const calls = []; + const result = await runM2Rendering({}, async context => { + calls.push(context.collectCoverage); + return {renderBudget:{withinBudget:true},coverageEntries:[]}; + }); + assert.deepEqual(calls,[false]); + assert.equal(result.measurements.renderQualification.withinBudget,true); + assert.equal('coverageDiagnostics' in result.measurements,false); +});