Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/architecture/rfcs/goal-workspace-surface-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions docs/architecture/rfcs/goal-workspace-surface-v0.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,13 @@ M3、W2/W3、安装态以及 L1/G1 的真实交付和原请求路线返回仍由
全局 Enter 到达前被重渲染替换。验证仍须在既有展开预算内展示全部 200 项。这项证据
验证浏览器驱动的目标选择;产品焦点恢复及早期部分清单的成因仍需各自的证据。

开启覆盖率时,源码运行现在先在不采集 V8 覆盖率的条件下完成 M2 资格:沿用普通模式下
每种切换中位数低于 1 秒、全部 15 个切换样本和原有首次渲染/展开上限。随后再执行一次
完整覆盖率旅程,分类、选择、无副作用、错误及远端来源检查继续阻断,计时作为诊断保留。
无采集资格失败时,组合运行在覆盖率旅程前结束,覆盖率结果不能替代它。验收结果同时保留
`renderQualification` 与 `coverageDiagnostics`;普通和打包运行只执行一次无采集资格。
该流程不改变负载或数值预算,也不重分类此前失败。

**检查点 2026-11-12(源码 coverage 执行)。**
源码分线现在执行真实的一次性首次 Goal 与运行就绪后端,不再提前返回仅适用于打包
模式的 PASS 回执。明确需要后端的旅程未完成读回时,在 PASS 前失败;其他场景的
Expand Down
1 change: 1 addition & 0 deletions examples/personal-workspace-browser-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
38 changes: 29 additions & 9 deletions examples/personal-workspace-browser/goal-workspace-m2.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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});
Comment thread
huangruiteng marked this conversation as resolved.
Resolved
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}`,
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -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) {
Expand Down
45 changes: 44 additions & 1 deletion examples/personal-workspace-browser/goal-workspace-m2.test.mjs
Original file line number Diff line number Diff line change
@@ -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]);

Expand Down Expand Up @@ -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);
});
Loading