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
31 changes: 31 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,37 @@ 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.

**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
backend journeys fail before PASS when their readback did not complete. Other
scenario coverage boundaries remain valid. Existing GET/HEAD socket recovery
includes EPIPE within the same three-attempt bound; HTTP writes stay single-attempt.
Research-record search checks bind help dismissal to its current keyboard trigger
and require the selected query to survive dismissal.

On immutable source `eedfc5e24`, local macOS/Node26 qualification passes all
86 scenarios across three non-overlapping 29/29/28 partitions with a common
catalog and source tree, verified backend-readback markers and nonempty merged
coverage. The passing partitions take 1,943.74 seconds; earlier failed runs,
setup and repair remain separate records. This is validation cost, with no
complete model/service/human cost comparison.

The matched 200-item M2 pair retains all 15 samples and long-task windows.
Ordinary medians are 225.77/172.99/310.77/128.45/146.52 ms, maximum 366.08 ms;
coverage medians are 328.21/371.20/332.49/174.73/173.02 ms, maximum 421.22 ms.
Both have zero samples above the 1-second target. Full-suite coverage M2 also
passes: medians 325.35/270.28/326.59/164.22/166.47 ms, maximum 463.88 ms.
The real 1,200 ms repeated Board delay is correctly rejected by ordinary
qualification (slow median 1491.77 ms, maximum 1519.98 ms; all 15
samples/windows retained). Existing strict-boundary and missing-sample tests
pass without a budget change.

The failed source partitions and historical frozen failures retain their original
status. This is named local source qualification, not Linux CI, an installed-host
upgrade, model task completion, kernel attribution or full-cost acceptance. V10
thresholds and the remaining real-team/product milestones retain their owners.

**Checkpoint 2026-10-11 (M1 proposed at `6c3c0c104` in [#6191](https://github.com/loopx-project/loopx/pull/6191), subsequently merged).**
Verified subset: V1 reducer fixtures written from Section 5.3, mutation checks
and the packaged page on synthetic status; V2 strip and Board sharing the work
Expand Down
24 changes: 24 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,30 @@ M3、W2/W3、安装态以及 L1/G1 的真实交付和原请求路线返回仍由
全局 Enter 到达前被重渲染替换。验证仍须在既有展开预算内展示全部 200 项。这项证据
验证浏览器驱动的目标选择;产品焦点恢复及早期部分清单的成因仍需各自的证据。

**检查点 2026-11-12(源码 coverage 执行)。**
源码分线现在执行真实的一次性首次 Goal 与运行就绪后端,不再提前返回仅适用于打包
模式的 PASS 回执。明确需要后端的旅程未完成读回时,在 PASS 前失败;其他场景的
合理 coverage 边界仍有效。既有 GET/HEAD socket 恢复纳入 EPIPE,仍至多三次;
HTTP 写请求只尝试一次。
研究记录搜索检查将提示关闭绑定当前键盘按钮,并要求关闭后保留原查询。

固定源码 `eedfc5e24` 在本地 macOS/Node26 下完成全部 86 场景资格:三个互不重叠的
29/29/28 分片共享同一完整场景目录和源码 tree,后端读回标记及非空合并 coverage 均已核对。
本次通过的分片用时 1,943.74 秒;此前失败运行、准备和修复仍分开记录。这是验证成本,
不构成完整模型、服务和人工成本比较。

同版本 200 项 M2 对照保留全部 15 个样本和长任务窗口。普通模式五组中位数为
225.77/172.99/310.77/128.45/146.52 ms,最大 366.08 ms;coverage 模式为
328.21/371.20/332.49/174.73/173.02 ms,最大 421.22 ms,两者超过 1 秒目标的
样本数均为 0。完整分片中的 coverage M2 也通过:中位数
325.35/270.28/326.59/164.22/166.47 ms,最大 463.88 ms。真实注入 1,200 ms 的重复看板
停顿被普通资格正确拒绝:慢组中位数 1491.77 ms、最大 1519.98 ms,全部 15 个
样本和窗口仍保留。既有严格边界及缺失样本负例通过,预算未变。

源码分片失败及历史冻结失败保留原判定。这是指定本地源码的资格,不证明 Linux CI、
安装宿主升级、模型任务完成、内核归因或完整成本验收。V10 阈值及剩余真实团队和产品
里程碑仍由各自既有 owner 验收。

**检查点 2026-10-11(M1 在 [#6191](https://github.com/loopx-project/loopx/pull/6191) 的 `6c3c0c104` 提出,后续已合并)。**
已验证的子集:V1 的按第 5.3 节编写的 reducer fixture、变异检查及合成状态打包页面;
V2 的指标条与看板共用工作判定;V4 的真实隔离 Chat 动作服务与注册表 authority 上的停止、
Expand Down
41 changes: 41 additions & 0 deletions docs/architecture/rfcs/monorepo-distribution-split-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -3468,3 +3468,44 @@ qualifies the consumed replan read cost; full CLI process, durable writeback,
model and end-to-end outcome costs are not inferred from these timings. The
complete minimal load/context closure, R1/M3, independent Standard packages,
workspace/meta publication and installed-host adoption remain open.


### 2026-11-12 — Dashboard source acceptance executes its backend journeys

Source coverage now executes the existing first-Goal and runtime-readiness
journeys through the real disposable Chat service. Both source and packaged
lanes already prepare the verified Chat bundle. Declared backend journeys
require successful backend assertions before a PASS receipt; an early return
cannot qualify them. Scenarios with other legitimate coverage boundaries keep
their existing behavior. The shared browser proxy adds EPIPE to its existing
three-attempt GET/HEAD socket recovery, recognizes typed errno or the first
Playwright socket-error line, and never retries an HTTP write or arbitrary prose.
Research-record search checks bind help dismissal to its current keyboard trigger
and require the selected query to survive dismissal.

On immutable source `eedfc5e24`, local macOS/Node26 qualification passes all
86 scenarios across three non-overlapping 29/29/28 partitions with a common
catalog and source tree, verified backend-readback markers and nonempty merged
coverage. The passing partitions take 1,943.74 seconds; earlier failed runs,
setup and repair remain separate records. This is validation cost, with no
complete model/service/human cost comparison.

The matched 200-item M2 pair retains all 15 samples and long-task windows.
Ordinary medians are 225.77/172.99/310.77/128.45/146.52 ms, maximum 366.08 ms;
coverage medians are 328.21/371.20/332.49/174.73/173.02 ms, maximum 421.22 ms.
Both have zero samples above the 1-second target. Full-suite coverage M2 also
passes: medians 325.35/270.28/326.59/164.22/166.47 ms, maximum 463.88 ms.
The real 1,200 ms repeated Board delay is correctly rejected by ordinary
qualification (slow median 1491.77 ms, maximum 1519.98 ms; all 15
samples/windows retained). Existing strict-boundary and missing-sample tests
pass without a budget change.

Earlier failed source partitions (EPIPE, query loss and temporary-directory
failure) remain recorded separately, as do historical frozen 1.162-second
failures and cancelled runs. This repairs
source-acceptance execution and its bounded transport recovery, with no render
threshold, kernel runtime or authority change. The first-Goal fixture uses an
exit-only Agent: registration and honest failed-Turn recovery do not prove
natural-language task completion or model benefit. Complete minimal closure,
R1/M3, workspace/meta publication, installed-host adoption and full-cost
acceptance remain open.
30 changes: 30 additions & 0 deletions docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2706,3 +2706,33 @@ dry-run 拒绝路径的中位数为 142.4→135.1 ms、934.4→911.8 ms。零条
这些计时推断完整 CLI 进程、持久写回、模型及端到端成果成本。完整最小加载/
上下文闭包、R1/M3、独立 Standard 包、workspace/meta 发布及安装宿主采用仍未
完成。


### 2026-11-12 — Dashboard 源码验收执行真实后端旅程

源码 coverage 现在通过真实的一次性 Chat 服务执行既有首次 Goal 和运行就绪旅程。
源码与打包分线均已准备经校验的 Chat bundle。声明需要后端的旅程必须完成真实后端
断言后才能记录 PASS,提前返回不能满足该条件;其他有合理 coverage 边界的场景
保留原有行为。共用浏览器代理在既有至多三次 GET/HEAD socket 恢复中纳入 EPIPE,
仅识别类型化 errno 或 Playwright 首行 socket 错误,不重试 HTTP 写请求或任意诊断文字。
研究记录搜索检查将提示关闭绑定当前键盘按钮,并要求关闭后保留原查询。

固定源码 `eedfc5e24` 在本地 macOS/Node26 下完成全部 86 场景资格:三个互不重叠的
29/29/28 分片共享同一完整场景目录和源码 tree,后端读回标记及非空合并 coverage 均已核对。
本次通过的分片用时 1,943.74 秒;此前失败运行、准备和修复仍分开记录。这是验证成本,
不构成完整模型、服务和人工成本比较。

同版本 200 项 M2 对照保留全部 15 个样本和长任务窗口。普通模式五组中位数为
225.77/172.99/310.77/128.45/146.52 ms,最大 366.08 ms;coverage 模式为
328.21/371.20/332.49/174.73/173.02 ms,最大 421.22 ms,两者超过 1 秒目标的
样本数均为 0。完整分片中的 coverage M2 也通过:中位数
325.35/270.28/326.59/164.22/166.47 ms,最大 463.88 ms。真实注入 1,200 ms 的重复看板
停顿被普通资格正确拒绝:慢组中位数 1491.77 ms、最大 1519.98 ms,全部 15 个
样本和窗口仍保留。既有严格边界及缺失样本负例通过,预算未变。

此前源码分片的 EPIPE、查询丢失和临时目录失败分别保留记录;历史冻结的 1.162 秒失败
及已取消运行也保持原判定。
本项修复源码验收执行与有界传输恢复,不改变渲染阈值、内核运行时或 authority。
首次 Goal 夹具使用退出式 Agent:登记及如实显示失败 Turn 的恢复不证明自然语言任务
完成或模型收益。完整最小闭包、R1/M3、workspace/meta 发布、安装宿主采用及完整成本
验收仍保持开放。
8 changes: 8 additions & 0 deletions examples/personal-workspace-browser-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ scenarioCatalog.push(completedHistoryIsolationScenario);
scenarioCatalog.push(agentModelSelectionScenario);
export { scenarioCatalog };

export function requireBackendReadback(scenario, result) {
if (scenario.requiresBackendReadback && result.backendReadbackVerified !== true) {
throw new Error(`Required backend readback did not execute: ${scenario.id}`);
}
}

async function main() {
const requestedScenario = process.env.LOOPX_PERSONAL_WORKSPACE_SCENARIO;
const scenarios = requestedScenario
Expand Down Expand Up @@ -207,10 +213,12 @@ async function main() {
? browser
: { newPage: (options = {}) => browser.newPage({ locale: "zh-CN", ...options }) };
const result = await scenario.run({ browser: scenarioBrowser, collectCoverage, url });
requireBackendReadback(scenario, result);
coverageEntries.push(...result.coverageEntries);
results[scenario.id] = {
status: "PASS",
note: result.note,
...(scenario.requiresBackendReadback ? {backend_readback_verified: true} : {}),
duration_ms: Date.now() - startedAt,
};
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { createInterface } from "node:readline";
import { resolveTestPython } from "../../scripts/test-python.mjs";
import { outputDir, packaged, repoRoot } from "./fixture.mjs";
import { outputDir, repoRoot } from "./fixture.mjs";
import { fetchWorkspaceBackend, openWorkspacePage } from "./scenario-context.mjs";

// End to end against the real service: a disposable `loopx chat` on a fresh
Expand All @@ -23,9 +23,10 @@ import { fetchWorkspaceBackend, openWorkspacePage } from "./scenario-context.mjs

export const firstGoalHomeLiveScenario = {
id: "first-goal-home-live",
requiresBackendReadback: true,
async run({ browser, collectCoverage, url }) {
// The real service serves the packaged bundle; the development lane has none to serve.
if (!packaged) return { coverageEntries: [], note: "live first-Goal home runs against the packaged bundle only" };
// Both lanes prepare the verified Chat bundle for the real backend. The
// source lane keeps Vite's source maps while proxying this same owner.
const root = await mkdtemp(join(tmpdir(), "loopx-first-goal-live-"));
const python = resolveTestPython({ repoRoot });
const child = spawn(python, ["-u", resolve(repoRoot, "examples/personal-workspace-browser/fresh-install-backend.py"), root], { cwd: repoRoot, env: { ...process.env, PYTHONPATH: repoRoot }, stdio: ["ignore", "pipe", "pipe"] });
Expand Down Expand Up @@ -130,7 +131,7 @@ export const firstGoalHomeLiveScenario = {
await page.locator(".personal-goal-row", { hasText: title }).first().waitFor({ state: "visible", timeout: 20_000 });
await firstGoal.waitFor({ state: "hidden", timeout: 20_000 });
await page.screenshot({ path: resolve(outputDir, "first-goal-home-live-home.png"), animations: "disabled" });
return { coverageEntries: await session.close(), note: "real loopx chat on a fresh runtime: zero-Goal discovery with its own warning; first Goal registered from the page through the real action owner with the stub Agent's failed first Turn reported honestly; registry written, directory lists it" };
return { coverageEntries: await session.close(), backendReadbackVerified: true, note: "real loopx chat on a fresh runtime: zero-Goal discovery with its own warning; first Goal registered from the page through the real action owner with the stub Agent's failed first Turn reported honestly; registry written, directory lists it" };
} catch (error) {
if (diagnostics) process.stderr.write(diagnostics);
await session?.close().catch(() => {});
Expand Down
14 changes: 12 additions & 2 deletions examples/personal-workspace-browser/research-record-search.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,19 @@ import {outputDir, repoRoot} from "./fixture.mjs";
import {startHistoryOwner} from "./research-history.mjs";

async function readEmptyScope(page, results, message) {
await results.getByRole('button',{name:'No matches in loaded records',exact:true}).focus();
const search = results.getByRole('searchbox');
const query = await search.inputValue();
const trigger = results.getByRole('button',{name:'No matches in loaded records',exact:true});
await trigger.focus();
// Enter opens the help even when the trigger retained focus after an earlier
// dismissal; focusing an already focused element does not fire onFocus.
await trigger.press('Enter');
await page.getByRole('tooltip').getByText(message,{exact:true}).waitFor();
await page.keyboard.press('Escape');
// Bind dismissal to the current trigger after rendering. A global Escape
// can reach the search input and clear the filter instead of only the help.
await trigger.press('Escape');
await page.getByRole('tooltip').waitFor({state:'hidden'});
assert.equal(await search.inputValue(), query, 'Dismissing scope help preserves the selected query');
}

export const researchRecordSearchScenario = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { join, resolve } from "node:path";
import { createInterface } from "node:readline";
import { waitForHttp } from "../dashboard-browser-smoke-support.mjs";
import { resolveTestPython } from "../../scripts/test-python.mjs";
import { outputDir, packaged, repoRoot } from "./fixture.mjs";
import { outputDir, repoRoot } from "./fixture.mjs";
import { fetchWorkspaceBackend, openWorkspacePage } from "./scenario-context.mjs";

// End to end against the real service: a disposable `loopx chat` whose Codex,
Expand All @@ -19,8 +19,8 @@ import { fetchWorkspaceBackend, openWorkspacePage } from "./scenario-context.mjs

export const runtimeReadinessLiveScenario = {
id: "runtime-readiness-live",
requiresBackendReadback: true,
async run({ browser, collectCoverage, url }) {
if (!packaged) return { coverageEntries: [], note: "live runtime readiness runs against the packaged bundle only" };
const root = await mkdtemp(join(tmpdir(), "loopx-readiness-live-"));
const python = resolveTestPython({ repoRoot });
const child = spawn(python, ["-u", resolve(repoRoot, "examples/personal-workspace-browser/runtime-readiness-backend.py"), root, "--with-goal"], { cwd: repoRoot, env: { ...process.env, PYTHONPATH: repoRoot }, stdio: ["ignore", "pipe", "pipe"] });
Expand Down Expand Up @@ -152,7 +152,7 @@ export const runtimeReadinessLiveScenario = {
const codex = catalog.adapters.find((row) => row.agent_id === "codex");
assert.equal(codex.available, true, "The real catalog now reports Codex available");
assert.equal(codex.unavailable_reason, undefined);
return { coverageEntries: await session.close(), note: "real loopx chat service, live PATH probe, real re-check" };
return { coverageEntries: await session.close(), backendReadbackVerified: true, note: "real loopx chat service, live PATH probe, real re-check" };
} catch (error) {
await session?.page.screenshot({ path: resolve(outputDir, "runtime-readiness-live-failure.png"), animations: "disabled" }).catch(() => {});
if (session?.errors.length) process.stderr.write(`${JSON.stringify(session.errors)}\n`);
Expand Down
7 changes: 6 additions & 1 deletion examples/personal-workspace-browser/scenario-context.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ export async function fetchWorkspaceBackend(route, backendUrl, { pathname } = {}
try {
return await route.fetch({ url: target.href, headers });
} catch (error) {
// Playwright preserves the native socket code in its first error line.
// Only that transport signature (or a typed errno), never response prose,
// permits the existing bounded read retry. Writes remain single-attempt.
const socketCode = error?.code ?? /^route\.fetch: (?:connect|read|write) (ECONNRESET|ECONNREFUSED|EPIPE)(?:\s|$)/u
.exec(String(error?.message ?? error).split("\n", 1)[0])?.[1];
const retryable = ["GET", "HEAD"].includes(request.method())
&& /\b(?:ECONNRESET|ECONNREFUSED)\b/u.test(String(error?.message ?? error))
&& ["ECONNRESET", "ECONNREFUSED", "EPIPE"].includes(socketCode)
&& attempt < 2;
if (!retryable) throw error;
await new Promise(resolve => setTimeout(resolve, 50 * (attempt + 1)));
Expand Down
Loading
Loading