From 4e684ba6bf0abcd67ba394b9e4feab5ee468ad6d Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Sat, 19 Sep 2026 02:45:58 +0800 Subject: [PATCH] docs(steward): read the confirm result as proven, and check the case against the run The product case still recorded gap 1 as "a confirmed plan does not distinguish committed / partial / all-gap / stale / rejected per lane" with the evidence "the only outcome sentence is the generic applied notice". That stopped being true when the team-plan result became a per-lane record: the scenario now finds `.personal-team-plan-result` naming the assigned lane and its Todo beside the unstaffed lane and its reason, and its printed gap list starts at `4-readiness`. So the case now says what the surface actually proves, including the limit it does not cross: the result says the assignment is recorded and points at the Goal for progress, so beat 5 (who can actually work) stays gap 1 instead of being absorbed into a proven confirm. The remaining gaps renumber to 1-4 in both locales. The table drifted within a day of the previous flip, so the scenario now checks it: the gap section of each locale must list exactly one row per gap the run recorded, and must not keep the retired confirm-result wording. The run is the only thing that knows which gaps are open, and review is not a mechanism that fires when a surface changes. Validation: steward-journey in development and packaged modes (beats=4, gaps=4-readiness,5-correction,6-recovery,7-return); the negative control (one extra row in the English table) fails with "lists 5 gaps but this run recorded 4 (4-readiness, 5-correction, 6-recovery, 7-return)"; docs-governance-smoke ok; docs-asset-integrity-smoke ok; loopx check --scan-path over the three changed files reports errors=0 and a clean boundary scan. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- docs/product/use-cases/steward/README.md | 25 +++++++++++------ .../product/use-cases/steward/README.zh-CN.md | 22 +++++++++------ .../steward-journey.mjs | 28 +++++++++++++++++-- 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/docs/product/use-cases/steward/README.md b/docs/product/use-cases/steward/README.md index 5ca3c3f47a..18a2e92d4b 100644 --- a/docs/product/use-cases/steward/README.md +++ b/docs/product/use-cases/steward/README.md @@ -24,10 +24,10 @@ fact the workspace surfaces render, not a claim about a live Goal. | 1 | Looks at the first screen | Goal board lanes (needs you / running / observing / scheduled), each Goal card naming its Agent and its next sentence | Proven | | 2 | Asks the steward in the Goal conversation | The steward's bounded prompt row (`询问下一步` / `向 Agent 获取进度报告` / `配置定时检查` / `看阻塞` / `查证据`) sends its message on click and leaves no draft, the ask becomes an accepted Turn, and the admitted team plan card lands in the same conversation | Proven | | 3 | Reads the card | Per lane: the Agent, the first bounded Todo with priority and action kind, the acceptance signal, and an explicitly unstaffed lane that keeps the work it did not staff; the quota envelope and stop condition; a statement that confirming is what creates the lanes | Proven | -| 4 | Confirms | Exactly one apply and one durable write; the card reports that LoopX state will refresh | Proven, but see gap 1 | -| 5 | Checks who can actually work | — | Gap 2 | -| 6 | Corrects or pauses one lane | — | Gap 3 | -| 7 | Waits for a lane to fail and asks who fixes it / judges completion | — | Gaps 4, 5 | +| 4 | Confirms | Exactly one apply and one durable write; the result names each assigned lane and each item left unstaffed with its reason, and says the execution progress is not what this result claims | Proven | +| 5 | Checks who can actually work | — | Gap 1 | +| 6 | Corrects or pauses one lane | — | Gap 2 | +| 7 | Waits for a lane to fail and asks who fixes it / judges completion | — | Gaps 3, 4 | Beats 5–7 are recorded by the scenario as typed gaps with the probe that looked for them. They are not "not implemented here" hand-waving: the scenario names @@ -74,11 +74,18 @@ gitignored. No live Goal, Agent, credential or local path is read or captured. | # | Gap | Evidence the scenario recorded | Owner surface | | --- | --- | --- | --- | -| 1 | A confirmed plan does not distinguish committed / partial / all-gap / stale / rejected per lane | probe: the only outcome sentence is the generic applied notice | steward plan commit (roadmap R1 remainder) | -| 2 | No per-lane readiness ladder (registered → bound → launchable → executing) | probe: no lane-readiness element or phrase | steward readiness (roadmap R2 / audit F6) | -| 3 | No lane-level correction (pause or supersede a confirmed commitment) | probe: no lane-correction element; only run steering exists | shared alignment (roadmap R4) | -| 4 | A failed lane does not name its blocker owner and next step | probe: no lane-blocker element or phrase | recovery/continuation (roadmap R3) | -| 5 | Completion is not judged by the lane's returned result | probe: no lane-return element or phrase | return delivery (roadmap R3) | +| 1 | No per-lane readiness ladder (registered → bound → launchable → executing) | probe: no lane-readiness element or phrase | steward readiness (roadmap R2 / audit F6) | +| 2 | No lane-level correction (pause or supersede a confirmed commitment) | probe: no lane-correction element; only run steering exists | shared alignment (roadmap R4) | +| 3 | A failed lane does not name its blocker owner and next step | probe: no lane-blocker element or phrase | recovery/continuation (roadmap R3) | +| 4 | Completion is not judged by the lane's returned result | probe: no lane-return element or phrase | return delivery (roadmap R3) | + +The confirm result used to be gap 1, recorded as "the only outcome sentence is +the generic applied notice". A confirmed plan now reports which work it +assigned, to whom, and what it left unstaffed and why, so the scenario asserts +that contract instead of treating it as a gap: the result must name every +assigned lane and every unstaffed item with its reason. What it still does not +claim is execution — the surface says the assignment is recorded and points at +the Goal for progress, which is why beat 5 remains gap 1 rather than proven. The steward prompt row used to be gap 1. The scenario now asserts it instead of probing it: the row must expose the five shipped labels, clicking `看阻塞` must diff --git a/docs/product/use-cases/steward/README.zh-CN.md b/docs/product/use-cases/steward/README.zh-CN.md index 19d9066d58..45b936b575 100644 --- a/docs/product/use-cases/steward/README.zh-CN.md +++ b/docs/product/use-cases/steward/README.zh-CN.md @@ -17,10 +17,10 @@ | 1 | 看首屏 | Goal 看板四条 lane(需要你/执行中/观察中/已安排),每张 Goal 卡给出 Agent 与下一步那句话 | 已证明 | | 2 | 在 Goal 对话里向管家提要求 | 管家的有界快捷提示行(询问下一步 / 向 Agent 获取进度报告 / 配置定时检查 / 看阻塞 / 查证据)点击即发送、不留草稿,这句话成为一个被接受的 Turn,被准入的团队计划卡落在同一个对话里 | 已证明 | | 3 | 阅读计划卡 | 每条 lane 的 Agent、第一刀 Todo(含优先级与 action kind)、验收信号,以及一条明确“未配齐”并保留未派工工作的 lane;配额包络与停止条件;以及“确认才会建 lane”的说明 | 已证明 | -| 4 | 确认 | 恰好一次 apply、一次 durable write;卡片提示 LoopX 状态将刷新 | 已证明,但见缺口 1 | -| 5 | 检查到底谁能干活 | — | 缺口 2 | -| 6 | 暂停或撤销某条 lane | — | 缺口 3 | -| 7 | 等某条 lane 失败,问谁负责修 / 用什么判定完成 | — | 缺口 4、5 | +| 4 | 确认 | 恰好一次 apply、一次 durable write;结果逐条点名已分配的工作与落在哪条 lane,以及哪些没派出去、原因是什么,并说明这份结果不主张执行进度 | 已证明 | +| 5 | 检查到底谁能干活 | — | 缺口 1 | +| 6 | 暂停或撤销某条 lane | — | 缺口 2 | +| 7 | 等某条 lane 失败,问谁负责修 / 用什么判定完成 | — | 缺口 3、4 | 第 5–7 拍由场景以 typed gap 记录,并带上“探针找过什么”的证据:不是“这里先不做”的一句话,而是列出了查找的选择器和文本、以及实际找到什么。 @@ -52,11 +52,15 @@ LOOPX_PERSONAL_WORKSPACE_SCENARIO=steward-journey \ | # | 缺口 | 场景记录的证据 | 归属面 | | --- | --- | --- | --- | -| 1 | 确认后不区分 committed / partial / all-gap / stale / rejected | 探针:只有一条通用的“已应用”提示 | 管家计划落地(roadmap R1 剩余项) | -| 2 | 没有 per-lane readiness 阶梯(registered → bound → launchable → executing) | 探针:无 lane-readiness 元素或文本 | 管家 readiness(roadmap R2 / 审计 F6) | -| 3 | 没有 lane 级纠偏(暂停或撤销已确认承诺) | 探针:无 lane-correction 元素;只有运行中 Turn 的纠偏 | shared alignment(roadmap R4) | -| 4 | lane 失败后不说明阻塞归属与下一步 | 探针:无 lane-blocker 元素或文本 | 恢复与继续(roadmap R3) | -| 5 | 不用 lane 的回传结果判定完成 | 探针:无 lane-return 元素或文本 | 交付回收(roadmap R3) | +| 1 | 没有 per-lane readiness 阶梯(registered → bound → launchable → executing) | 探针:无 lane-readiness 元素或文本 | 管家 readiness(roadmap R2 / 审计 F6) | +| 2 | 没有 lane 级纠偏(暂停或撤销已确认承诺) | 探针:无 lane-correction 元素;只有运行中 Turn 的纠偏 | shared alignment(roadmap R4) | +| 3 | lane 失败后不说明阻塞归属与下一步 | 探针:无 lane-blocker 元素或文本 | 恢复与继续(roadmap R3) | +| 4 | 不用 lane 的回传结果判定完成 | 探针:无 lane-return 元素或文本 | 交付回收(roadmap R3) | + +确认结果原为缺口 1,当时的证据是“只有一条通用的『已应用』提示”。现在确认后的结果会 +说明它分配了哪些工作、分给谁、哪些没有派出去以及原因,因此场景改为对它做断言而不是记为 +缺口:结果必须点名每一条已分配的 lane 与每一项未派工的工作及其原因。它仍然不主张执行—— +界面说的是“分配已记录,执行进度请查看目标”,所以第 5 拍仍是缺口 1,而不是已证明。 管家快捷提示行原为缺口 1。现在场景对它做断言而不是探针:提示行必须出现五个已发布标签, 点击“看阻塞”必须把消息作为一次被接受的 Turn 发出,输入区必须保持为空。回归时失败信息 diff --git a/examples/personal-workspace-browser/steward-journey.mjs b/examples/personal-workspace-browser/steward-journey.mjs index 9aee008862..f271014986 100644 --- a/examples/personal-workspace-browser/steward-journey.mjs +++ b/examples/personal-workspace-browser/steward-journey.mjs @@ -10,10 +10,10 @@ // Everything here is synthetic: the fixture substitutes the agent turn, and no // live workspace, Goal, agent or local path is read or captured. -import { writeFile } from "node:fs/promises"; +import { readFile, writeFile } from "node:fs/promises"; import { resolve } from "node:path"; -import { outputDir, packaged } from "./fixture.mjs"; +import { outputDir, packaged, repoRoot } from "./fixture.mjs"; import { openWorkspacePage } from "./scenario-context.mjs"; const GOAL_ID = "product-release"; @@ -345,6 +345,30 @@ export const stewardJourneyScenario = { console.log( `steward-journey mode=${servedMode} beats=${beats.length} gaps=${gapBeats.join(",") || "none"}`, ); + + // The product case is the owner-facing reading of this run, and it drifted + // silently once already: a lane delivered the confirm result while the + // table still called it a gap. The tables are checked here rather than in + // review because the run is the only thing that knows which gaps are open. + for (const [caseDoc, gapHeading] of [ + ["docs/product/use-cases/steward/README.md", "Recorded Gaps And Owners"], + ["docs/product/use-cases/steward/README.zh-CN.md", "已记录缺口与归属"], + ]) { + const text = await readFile(resolve(repoRoot, caseDoc), "utf8"); + const headingAt = text.indexOf(`## ${gapHeading}`); + check(headingAt >= 0, `${caseDoc} keeps its gap section heading`); + const section = headingAt < 0 ? "" : text.slice(headingAt).split(/\n## /)[0]; + const gapRows = section.split("\n").filter((line) => /^\| \d+ \| /.test(line)); + check( + gapRows.length === gapBeats.length, + `${caseDoc} lists ${gapRows.length} gaps but this run recorded ${gapBeats.length} (${gapBeats.join(", ") || "none"})`, + ); + check( + !text.includes("the only outcome sentence is the generic applied notice") + && !text.includes("只有一条通用的“已应用”提示"), + `${caseDoc} still keeps the confirm-result gap this run proves present`, + ); + } } finally { await context.close(); }