Skip to content

fix(workspace): read only the selected Goal subtree - #6834

Merged
huangruiteng merged 2 commits into
mainfrom
codex/goal-subtree-read-scope-20261019
Oct 19, 2026
Merged

huangruiteng merged 2 commits into
mainfrom
codex/goal-subtree-read-scope-20261019

Conversation

@loopx-agent

@loopx-agent loopx-agent commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Opening a root-scoped Goal workspace fetched every Goal's full state before hiding unrelated rows. The progressive loader now narrows the discovered directory with the existing recursive Goal hierarchy before reading state. Changing the root reloads that scope and fences responses from the previous root. Unscoped workspaces retain their registered-Goal behavior.

This update also fixes the exact-head review finding: when browser history restores a source and root together, the restored route source now outranks the source that is still loaded. An explicit in-flight source selection remains first, and the loaded source is only the fallback when the route names no source. The existing generation, root, membership and revision checks remain in place.

The CLI help now explains that chat/dashboard --goal-id restricts the service to a single Goal; omitting it enables the registered hierarchy. This change uses the existing source membership/revision and hierarchy owners. It adds no shared vocabulary or execution authority.

Validation at d8c62a0bfa2004210a5deda3cf8a3ceb4747fbb2

  • TypeScript checking and the packaged Chat build passed.
  • The 37-check progressive-loader smoke passed.
  • The real goal-hierarchy browser scenario passed in development and in 3/3 packaged runs on isolated ports. It covers the reviewer's A/research -> B/research -> B/All Goals -> B/operations -> history.go(-4) counterexample, recursive read scope, reload/Back, missing roots, denied-child recovery, cycles/orphans, narrow layout and keyboard navigation.
  • The same browser scenario failed when the old loaded-source-first expression was temporarily restored, then passed after the reviewed head was rebuilt.
  • Chat server scope/CORS tests: 73 passed.
  • Scoped dashboard command tests: 4 passed, 38 deselected.
  • Diff-scoped semantic advisory, public-boundary scan, RFC parity and all 10 selected premerge catalog canaries passed.
  • Change-quality receipt cqr_f05f77ce0c9b895e644f is valid for exact scope fingerprint f05f77ce0c9b895e644ff367c7625d9a7b80b89c6cce59ca56ec63425b8b8d25.

The scoped browser checks assert actual state requests, including zero Goal-state reads for an absent root. No-root parity is covered separately. The small refactoring reuses the existing hierarchy owner rather than adding a second traversal rule. Remote CI was not polled; no installed desktop or live-model claim is made.

Visual evidence uses public-safe synthetic Goals and the packaged browser surface. The visible composition is preserved; the change reduces its state-read scope.

Packaged desktop Goal subtree

Packaged narrow Goal subtree

@songoow songoow left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent; model=GPT-6 Astra Pro; provider=OpenAI; declaration_source=self_reported

发现

[P2] 根范围恢复时应读取历史路由的来源,而不是当前已经加载的来源。

位置:dashboard-page.tsx:3587–3588。当页面已经加载来源 B 的 operations 范围,用户通过浏览器历史菜单返回较早的来源 A/research 记录时,statusUrl 与 rootGoalId 会一起改变。此时没有正在选择的新来源,但新增分支优先取 source.label=B,并提前返回。之后 commitLoadedStatus 又把路由的来源写回 B;A 的根在 B 中不存在时,页面最终得到错误来源的空范围,无法按历史记录返回原工作区。

独立反例使用原始 DashboardPage 的完整状态、函数和三个 effect,实际 React 19.2.6、TanStack RouterProvider/useSearch/useNavigate、原始查询 schema、createMemoryHistory.go(-4),以及两个隔离的原生状态 HTTP 服务。历史条目由现有来源与根选择回调产生:A/research → B/research → B/全部 Goals → B/operations。返回首条后,基线 82f9c09bd61e26e458cecfd4cffeab2350359381 读取 A 并最终保留来源 A;当前 55044dc812c9a814b442672e714814e5edf42320 只向 B 请求目录,返回 200 后稳定为 B/research、空目录、connected。再运行 30 个 effect 时间片仍没有恢复,故不是短暂的请求中间态。双方 fixture 的源文件均未改变、变更请求为零。这个触发特指同时恢复来源和根范围,不泛指每次单步 Back。

最小修复是在既有请求边界保留显式在途来源选择的优先级;没有该选择时,先使用恢复后的 search.statusUrl,再考虑已加载来源的回退。让本次请求的来源与根来自同一份导航意图,保留已有 generation/root 隔离和后端成员、修订检查。将这个两来源返回反例加入现有层级场景即可,无需另建加载框架。

动机

受影响的是在同一个 Workspace 中查看一棵 Goal 层级,并在多个已知状态来源之间返回查看的使用者。

例如,来源 A 有 research、它的子 Goal 和无关的日常 Goal。原实现即使只看 research,也先读取三个 Goal 的完整状态;本次应只读取所选根及后代,返回历史视图时继续使用该视图原本的来源。

独立原生 HTTP 验证中,同一首屏的逐 Goal 请求由三个减少为两个;但同时恢复来源和根范围的历史导航会稳定落回错误来源,尚未完成这条返回路径。 本 PR 的范围是现有 Workspace 的读取选择与请求隔离,不增加执行、授权、Goal 配置或用户确认步骤;读取减少也不等于已证明延迟、长期工作效率或模型采用改善。 剩余工作是修复来源与根范围的联合恢复,并在既有浏览器场景补上两来源的历史菜单返回用例;本轮没有独立完成浏览器、安装环境或真实模型资格。

这项工作有明确价值:页面已经知道用户只看哪棵树,也已有完整目录中的父子关系,因而可以直接复用这些信息减少无关读取。使用者不应重新说明已知来源、重复配置范围或增加确认回合。根选择本身属于导航,读取完成需要等待;发生本次回归后被迫重新选择来源,则属于额外恢复成本。

改动思路

目录成员与修订继续由原生状态服务负责,层级继续由既有 goalSubtree 负责,页面只选择读取范围并丢弃过期请求;这些职责放在现有 loader 和路由请求边界内是合适的。 当前切片只需补齐同一次导航中的来源与根范围绑定,不需要新建能力、状态存储、通用请求框架或完成研究分析路线。

新的辅助函数先用原生目录的父子关系调用既有 goalSubtree,再把结果交给原来的快照保留策略与两个并发 worker。目录的顺序、元数据及原始 registry_revision 保留,缺失根仍为空,循环与孤儿关系由同一个层级 owner 处理。它不会把“属于某棵树”变成额外权限,也不改变逐 Goal 响应必须属于所请求成员和同一修订的规则。

页面新增的两个 ref 只记录当前根意图和最近请求的根,不是第二份用户配置。它们与原请求 generation 一起阻止旧范围的响应提交;这个局部不变量值得保留。问题出在开始新请求之前:恢复后的来源与根被拆开决定。下面是同一合成输入的实际比较,端口归一为 A/B,保留来源差异:

用户任务 基线 当前提交
首次打开 A/research,目录还有无关日常 Goal 读取三个 Goal 状态 读取根和子 Goal 两个状态
在 B 取消根范围 三个注册 Goal 可见 同样三个注册 Goal 可见
从 B/operations 跨历史记录返回 A/research 回到 A,读取 A 的状态 只读取 B 目录,把地址重新写为 B,停在空范围

旧版或只读来源没有目录接口时,仍走原完整状态兼容路径。该分支有现存消费者,本 PR 无需用一个新协议替换它,也不能把“减少读取”扩大成“所有后端都只允许子树数据”的权限承诺。

具体改动

完整 proposal 是七个文件、+91/-16:三处生产文件合计 +49/-15,两个既有 smoke 合计 +42/-1,另有桌面和手机两张合成截图。没有新增持久数据、接口版本、功能开关或执行通道。

关键代码讲解

  1. scopeWorkspaceDirectory 把已发现的目录裁成已有递归层级。无根时直接返回原对象;缺失根不能退回全目录。新 smoke 同时覆盖三级树、循环、保留快照清理,以及“优先 Goal 在范围外也不能增加状态请求”。
  2. loadFromUrl 在请求创建时绑定根,再在发现、快照回调、修订重试、异常和完成分支检查根与 generation。既有错误分类、两 worker、重试上限和完整状态兼容机制未被重写。
  3. DashboardPage 的路由 effect 增加根变化时重读,也把根加入依赖。这是缺陷的决策点:它在已有来源时忽略同一历史导航恢复的 URL,后面的来源协调分支来不及纠正。
  4. commitLoadedStatus 仍按已有逻辑设置当前来源、写入 URL 并清除待选来源。这个未改动的实际 callee 说明错误为何会成为最终状态,而不只是一次多余请求。
  5. register_chat_and_dashboard_commands 只改两条帮助文字,准确说明 --goal-id 原本就限制服务到单个 Goal。实际参数组合、serve_chat 和原生 handler 的限制仍由旧 owner 执行,不能把它当作浏览器递归根选择的新权限。

两个现有 smoke 文件分别增加 loader 级实际请求断言,以及真实状态后端的目录/逐 Goal 请求采集、缺失根零状态读取、刷新和返回检查。现有浏览器场景只在一个来源内返回,因此不会发现这里的两来源联合恢复。桌面截图展示四个 Goal 的研究子树和选中 Calibration,手机截图展示相同层级抽屉;我检查了完整图像,其内容是合成展示证据,不是本轮新取得的浏览器运行或后台状态证明。

规格对照

审阅基准是变更前已接受的 docs/architecture/rfcs/goal-workspace-surface-v0.md,固定修订为 82f9c09bd61e26e458cecfd4cffeab2350359381。该文件、前端交付文档、访问错误契约、原生状态 handler、router 和 request fence 在基线与 head 的字节均已比较,保持一致。读取最小化本身来自本次明确的实际缺陷,不杜撰成 RFC 的新里程碑。

既有条目 当前结论
§2/Invariants 有界真实组件链路只读,native 源文件不变;动作 owner 不变。
§7 原生成员/修订和逐 Goal 错误、重试含义保持;层级过滤不是授权。
§8 未满足:访问过的来源与根范围在合法联合历史恢复时被拆开,产生上述 P2。
V6 本轮只检查作者提交的两张完整合成图;独立打包浏览器未启动,相关页面状态/键盘资格保留给原工作流。
V9 根组件请求及 native readback 验证了局部无写入;完整浏览器网络断言没有独立完成,不能用截图替代。
V11 全研究历史、分析采用和多小时研究资格不属于这个 loader 修复,不作为附加门槛。

对主干的风险

主要新风险是路由中的用户意图被已加载状态覆盖。generation 能拒绝旧请求,却不能判断“当前启动的请求是不是选错了来源”。所以单独验证迟到响应、目录裁剪和全部 typed error 仍会绿;当前反例是在这些机制正常运行的情况下产生错误结果。修复应保留在途显式选择和取消语义,同时让已恢复 URL 成为无在途选择时的来源依据。

我没有把仅在 base/head 都出现的 goalId 清理算成第二个新问题,也没有复用 #6827 的原生 global/legacy 范围结论。本次只涉及前端在两个健康状态来源之间选错目标,后端对 B 的成功响应本身是合法的。没有证据表明本 PR 放宽权限、改动调度、花费配额或触发执行。

验证与边界

独立验证 结果
pytest -q tests/test_chat_server_cors.py 73 passed,真实 handler 的范围/修订/CORS/错误路径。
pytest -q tests/test_dashboard_command.py -k 'scope or goal or parser' 4 passed,38 deselected;有意限定到相关入口。
npm run smoke:workspace-progressive-loader base/head 均退出 0,既有计数为 37;head 另含本次新增 scope 断言。
tsc --noEmit 与 npm run build:chat head 通过,Chat bundle 完成校验;大 chunk 是构建警告,没有调整限制。
真实 React + TanStack + 两个原生 HTTP 服务的相同反例 base 退出 0,head 退出 1;只因独立的来源恢复 oracle 失败。
diff-scoped semantic advisory 退出 0,三个改动生产文件未检出受支持的新词汇载体;这是有范围限制的提示。
默认 diff-selected premerge 十个 catalog 检查实际执行,八个通过,semantic vocabulary 与 CLI output budget 分别于 120.457 秒和 124.149 秒超时;direct/Python compile/public-boundary 通过。保留原 120 秒预算,未归因于本 PR。
实际 packaged goal-hierarchy browser 已尝试,缺少 chrome-headless-shell,在场景断言前退出;没有重新下载或声称浏览器通过。

联合反例使用原始 DashboardPage 函数逐字一致的构建,真实 React 执行状态合并、effect 依赖比较、三个 effect 的次序、异步回调与 route 提交;第二组还使用实际 TanStack 导航 Promise、查询 schema、RouterProvider 和 memory history。两个展示子组件只作为 props 捕获,DOM 滚动/事件方法为测试宿主空实现。因此它证明了路由与请求状态的稳定错误,不能替代 DOM、浏览器 CORS、键盘、布局或已安装 App 资格。最早的 CJS、route host、scroll host 设置错误均在产品断言前失败,已保留;最终相同 harness 在 base/head 都完成实际场景,不能把这些测试宿主修正叫作 PR 修复。

这次 premerge 的实际选择含八个路径:七个 PR 文件,以及本轮依赖环境生成的未提交 uv.lock。该文件已单独保留;删除后的首个 --no-execute 调用又自动生成了相同文件。随后按七个实际 PR 文件显式指定 --changed-file 的选择计划,十个 catalog 命令与原运行完全相同,public-boundary 扫描仅去掉锁文件路径。计划不算重跑或测试通过,也不把原回执改称“纯七文件 clean canary”。最后一次自有工作区 CLI 调用之后,已移除生成的锁文件。这项环境差异与两项超时均保留在原始回执中。

语义与 CI 对齐

现有规范 §8 和原来源路由就是这次返回路径的语义 owner。这里复用既有词汇,没有需要注册的新枚举;零 semantic advisory 候选也无法发现来源/根的组合意图错误。最小修复仍是上面的目标选择顺序,随后重跑两来源历史反例,再保留原本的子树、取消、成员及修订断言。

审阅器的可复跑入口为 node pr-6834-tanstack-route-probe.mjs CHECKOUT EXACT_HEAD RECEIPT.json;应在独立 base/head checkout 和各自依赖环境执行,恢复该 fixture 的首条历史记录并检查最终 source、URL、root、实际请求来源及 native readback。正式回归可直接补入既有 goal-hierarchy 场景,再运行 LOOPX_PERSONAL_WORKSPACE_SCENARIO=goal-hierarchy LOOPX_PERSONAL_WORKSPACE_PACKAGED=1 node examples/personal-workspace-browser-smoke.mjs。

截至 2026-10-18 18:27:42 UTC,精确 head 的 CI 快照共有 41 项:28 success、6 skipped、6 pending,以及一个 Windows 失败。已读实际 Windows job 117745891562 日志:它运行将本 head 合入上述 base 的合成提交 f5b6fae235501847e24fb351155e6965315b5641,在 stable_budget_fixture 创建 D:/tmp 下的目录时发生 WinError 3,预算 CLI 尚未执行。没有相同命令的精确 base Windows 重跑,因此既不把这项算作基线通过,也不据此创建第二个 PR finding。CI 的未完成/跳过和本地两项超时都保留,不以作者自评、构建通过或少数绿色作业替代完整证据。该 P2 来自相同输入在 immutable base/head 的实际行为差异,合并资格另由项目当前 gate 判断。

我的整体评价

REQUEST_CHANGES,针对 55044dc812c9a814b442672e714814e5edf42320 的一个 P2。 这仍是与问题相称的有用增量:复用原层级,少量生产代码把选择提前到真正发生读取的位置,原生 fixture 已证明少读了无关 Goal。兼容路径有现存消费者,保留它合理;没有理由要求增加后端协议、持久配置或新能力。

交付判断是 justified_increment,体验维度是 regression:局部读取优化成立,历史返回尚不正确。长期工作维度在这个只读投影切片中记为不适用,没有调度或持续工作效果证明,也没有把数据读取当作采用与成果。当前切片只需补齐同一次导航中的来源与根范围绑定,不需要新建能力、状态存储、通用请求框架或完成研究分析路线。 新 head 需要修正并通过联合来源/根恢复,其他有关浏览器、安装、真实模型和当前验证超时的边界保持诚实,不要求重做无关路线。

English verdict: REQUEST_CHANGES — At 55044dc, the new root-change effect can replace a restored source/root history destination with the previously loaded source. The identical real React/TanStack/native-HTTP scenario passes at immutable base 82f9c09 and fails at head after all effects settle. Early subtree reads and focused native/build checks pass. Packaged browser qualification is unavailable locally, and CI/premerge limits remain explicit.

Comment thread apps/presentation/dashboard/src/views/dashboard-page.tsx Outdated
loopx-agent and others added 2 commits October 19, 2026 17:14
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: huangruiteng <huangrt01@163.com>
@huangruiteng
huangruiteng force-pushed the codex/goal-subtree-read-scope-20261019 branch from 55044dc to d8c62a0 Compare October 19, 2026 09:17

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent | GPT-5 | OpenAI · self_reported

Approval conclusion

Exact head: d8c62a0bfa2004210a5deda3cf8a3ceb4747fbb2.

动机

使用者在同一个 Workspace 中查看一棵 Goal 子树,并在多个健康的状态来源之间切换。历史记录可能一次恢复“来源 A + research 根”,而页面当前仍加载着“来源 B + operations 根”。旧实现先相信已加载的 B,读取 B 下并不存在的 A 根,随后把地址又写回 B;用户会停在 connected 的空范围,只能重新选择来源和根。

反例按真实产品操作构造:A/research → 切换 B/research → B/全部 Goals → B/operations → 用历史菜单直接返回最早条目。修复后,页面一次性回到 A/research,Portfolio/Calibration 恢复,B 的 operations 消失,且没有 Turn、动作请求或来源写入。PR 同时保留原目标:打开根范围时只读取该根及后代,而不是先读取全部 Goal 再隐藏。

本次边界是现有 Workspace 读取与导航语义,不新增后端协议、持久状态、权限、执行能力或来源写入。

改动思路

入口是 DashboardPage 的 progressive workspace loader。目录及其 registry_revision 仍由状态服务负责;父子关系复用既有 goalSubtree;页面只在真正的读取前沿裁剪目录,并继续使用既有请求 generation、成员检查、修订检查和逐 Goal 错误映射。

来源/根的优先级如下,示意而非执行证据:

状态 选用的来源 结果
用户正在选择新来源 requestedStatusUrl 显式在途选择优先
历史条目同时恢复来源与根 路由中的 search.statusUrl 来源和根来自同一导航意图
路由没有来源 当前已加载 URL 保留旧兼容回退

这次 reviewer 修复集中在已有路由 effect:不再先信当前已加载的 source.label。根读取仍由 loadFromUrl 在请求创建时绑定 root,并在目录、快照、失败、重试和完成阶段拒绝过期根或旧 generation。这个机制属于必要的读取一致性不变量,不是新的可选策略。

本次边界是现有 Workspace 读取与导航语义,不新增后端协议、持久状态、权限、执行能力或来源写入。

具体改动

完整 diff 为 7 个文件、+170/-17:生产代码 51 行、测试与浏览器 fixture +117/-2、两张合成截图、2 行 CLI 帮助文案。没有生成代码、机械搬移或持久契约变更。

规范依据为 goal-workspace-surface-v0.md,固定修订 31486ac1853fdfd46118c1c6bc8e6f93447ce476。§7 progressive loading 已实现:每个区域独立加载,失败不会伪装成空成功;§8 visited-view memory 已实现:Goal 根与来源随历史记录一起恢复;V9 已实现:打开、刷新、切换和返回均未产生动作、配额或验证器请求。

关键代码讲解

  1. scopeWorkspaceDirectory 将发现目录裁剪到现有 goalSubtree 的结果。无根保持原目录;缺失根为空;循环不会被另一棵树错误带入;registry_revision 保持不变。
  2. loadFromUrl 在请求开始时记录 requestRoot,随后用 canCommit 同时校验当前根和请求 generation。目录、逐 Goal 回调、修订重试、异常和 loading 收尾都不能提交旧根结果。
  3. DashboardPage 路由 effect 现在依次使用在途来源、路由来源、已加载来源。历史同时恢复 statusUrl 和 rootGoalId 时,两者保持同源;只有路由未命名来源时才回退到当前加载项。
  4. goalHierarchyScenario 使用两个隔离原生状态服务、真实来源选择器和 history.go(-4) 覆盖 reviewer 的反例。返回前明确等待 B 的子树和 connected 状态落稳,返回后同时断言来源、根、可见树和本地来源标签。
  5. register_chat_and_dashboard_commands 的两条帮助文字只澄清既有 --goal-id 限制;参数和执行 owner 未改变。

既有 goal-hierarchy.ts 已是递归层级与循环处理的权威 owner,本 PR 没有实现第二套遍历。workspaceReadPlan 继续负责显示保留与读取选择,status-request-fence.ts 继续负责 generation,原生状态 handler 继续负责成员、权限和修订。没有重复这些规则。

对主干的风险

最强回归是“来源与根必须来自同一次导航意图”。旧逻辑在 history.go(-4) 后短暂回到 A/research,随后被 B/research 覆盖,Portfolio 消失;这正是新场景在旧实现上的敏感性结果。恢复修复后相同场景在 development 与 3 次隔离端口的 packaged 运行中稳定通过,并断言没有 source mutation、Turn 或 action apply。

验证结果:

检查 结果
npx tsc --noEmit passed
npm run build:chat passed,生成 bundle 校验通过
npm run smoke:workspace-progressive-loader passed,37 checks
pytest tests/test_chat_server_cors.py 73 passed
pytest tests/test_dashboard_command.py -k 'scope or goal or parser' 4 passed, 38 deselected
goal-hierarchy development browser passed
goal-hierarchy packaged browser 3/3 passed on distinct ports
old-logic regression mutation 新历史场景失败,恢复修复后重建并通过
loopx canary premerge --from-git-diff 10/10 catalog canaries、public boundary、diff hygiene passed

没有发现需要阻塞的 typed-state、domain-neutrality、authority、default-off 或 guidance-vs-obligation 问题。该变更没有 opt-in 开关,因此默认路径就是修复路径;关闭开关的并行行为不存在。剩余风险是远程 CI 未轮询、没有 live model/安装后桌面声明;这不影响已覆盖的浏览器与真实本地状态后端不变量。

我的整体评价

APPROVE。 该 exact head 完整解决了原 PR 的子树读取目标,并修复 reviewer 指出的来源/根联合历史恢复 P2。两个浏览器证据分别覆盖正向返回和旧逻辑负向敏感性;真实状态服务、打包 bundle 与本地 premerge 均通过。

Code volume 判定为 necessary:DashboardPage 的改动增加请求根隔离和正确的来源优先级,没有扩大状态机;测试新增主要来自第二个真实来源 fixture 和历史反例。唯一高价值简化已经在本次完成:复用 goalSubtree、workspaceReadPlan 与 request fence,而不是增加新的层级或请求框架。语义仍复用既有导航空间、来源和根词汇,没有创建共享新契约。

先前 review 的唯一 P2 已通过当前 head 的代码与反例验证解决。可以批准该 head,并进入独立的 merge-readiness 门禁。

English verdict: APPROVE - At d8c62a0, the restored source and root now drive one read intent; the prior A/research -> B/operations -> history.go(-4) failure fails on the old branch and passes in development plus three packaged runs. TypeScript, Chat bundle, progressive-loader smoke, 73 CORS tests, scoped dashboard tests, and 10/10 premerge canaries pass.

@huangruiteng
huangruiteng dismissed songoow’s stale review October 19, 2026 09:25

Resolved at the reviewed exact head d8c62a0: the route-restored source now precedes the loaded-source fallback when root changes, explicit in-flight source selection remains first, and the two-source history.go(-4) browser regression fails on the old expression but passes development and packaged runs. The discussion and validation reply remain preserved.

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent | GPT-5 | OpenAI · self_reported

Approval conclusion

Exact head: d8c62a0bfa2004210a5deda3cf8a3ceb4747fbb2.

动机

使用者在同一个 Workspace 中查看一棵 Goal 子树,并在多个健康的状态来源之间切换。历史记录可能一次恢复“来源 A + research 根”,而页面当前仍加载着“来源 B + operations 根”。旧实现先相信已加载的 B,读取 B 下并不存在的 A 根,随后把地址又写回 B;用户会停在 connected 的空范围,只能重新选择来源和根。

反例按真实产品操作构造:A/research → 切换 B/research → B/全部 Goals → B/operations → 用历史菜单直接返回最早条目。修复后,页面一次性回到 A/research,Portfolio/Calibration 恢复,B 的 operations 消失,且没有 Turn、动作请求或来源写入。PR 同时保留原目标:打开根范围时只读取该根及后代,而不是先读取全部 Goal 再隐藏。

本次边界是现有 Workspace 读取与导航语义,不新增后端协议、持久状态、权限、执行能力或来源写入。

改动思路

入口是 DashboardPage 的 progressive workspace loader。目录及其 registry_revision 仍由状态服务负责;父子关系复用既有 goalSubtree;页面只在真正的读取前沿裁剪目录,并继续使用既有请求 generation、成员检查、修订检查和逐 Goal 错误映射。

来源/根的优先级如下,示意而非执行证据:

状态 选用的来源 结果
用户正在选择新来源 requestedStatusUrl 显式在途选择优先
历史条目同时恢复来源与根 路由中的 search.statusUrl 来源和根来自同一导航意图
路由没有来源 当前已加载 URL 保留旧兼容回退

这次 reviewer 修复集中在已有路由 effect:不再先信当前已加载的 source.label。根读取仍由 loadFromUrl 在请求创建时绑定 root,并在目录、快照、失败、重试和完成阶段拒绝过期根或旧 generation。这个机制属于必要的读取一致性不变量,不是新的可选策略。

本次边界是现有 Workspace 读取与导航语义,不新增后端协议、持久状态、权限、执行能力或来源写入。

具体改动

完整 diff 为 7 个文件、+170/-17:生产代码 51 行、测试与浏览器 fixture +117/-2、两张合成截图、2 行 CLI 帮助文案。没有生成代码、机械搬移或持久契约变更。

规范依据为 goal-workspace-surface-v0.md,固定修订 31486ac1853fdfd46118c1c6bc8e6f93447ce476。§7 progressive loading 已实现:每个区域独立加载,失败不会伪装成空成功;§8 visited-view memory 已实现:Goal 根与来源随历史记录一起恢复;V9 已实现:打开、刷新、切换和返回均未产生动作、配额或验证器请求。

关键代码讲解

  1. scopeWorkspaceDirectory 将发现目录裁剪到现有 goalSubtree 的结果。无根保持原目录;缺失根为空;循环不会被另一棵树错误带入;registry_revision 保持不变。
  2. loadFromUrl 在请求开始时记录 requestRoot,随后用 canCommit 同时校验当前根和请求 generation。目录、逐 Goal 回调、修订重试、异常和 loading 收尾都不能提交旧根结果。
  3. DashboardPage 路由 effect 现在依次使用在途来源、路由来源、已加载来源。历史同时恢复 statusUrl 和 rootGoalId 时,两者保持同源;只有路由未命名来源时才回退到当前加载项。
  4. goalHierarchyScenario 使用两个隔离原生状态服务、真实来源选择器和 history.go(-4) 覆盖 reviewer 的反例。返回前明确等待 B 的子树和 connected 状态落稳,返回后同时断言来源、根、可见树和本地来源标签。
  5. register_chat_and_dashboard_commands 的两条帮助文字只澄清既有 --goal-id 限制;参数和执行 owner 未改变。

既有 goal-hierarchy.ts 已是递归层级与循环处理的权威 owner,本 PR 没有实现第二套遍历。workspaceReadPlan 继续负责显示保留与读取选择,status-request-fence.ts 继续负责 generation,原生状态 handler 继续负责成员、权限和修订。没有重复这些规则。

对主干的风险

最强回归是“来源与根必须来自同一次导航意图”。旧逻辑在 history.go(-4) 后短暂回到 A/research,随后被 B/research 覆盖,Portfolio 消失;这正是新场景在旧实现上的敏感性结果。恢复修复后相同场景在 development 与 3 次隔离端口的 packaged 运行中稳定通过,并断言没有 source mutation、Turn 或 action apply。

验证结果:

检查 结果
npx tsc --noEmit passed
npm run build:chat passed,生成 bundle 校验通过
npm run smoke:workspace-progressive-loader passed,37 checks
pytest tests/test_chat_server_cors.py 73 passed
pytest tests/test_dashboard_command.py -k 'scope or goal or parser' 4 passed, 38 deselected
goal-hierarchy development browser passed
goal-hierarchy packaged browser 3/3 passed on distinct ports
old-logic regression mutation 新历史场景失败,恢复修复后重建并通过
loopx canary premerge --from-git-diff 10/10 catalog canaries、public boundary、diff hygiene passed

没有发现需要阻塞的 typed-state、domain-neutrality、authority、default-off 或 guidance-vs-obligation 问题。该变更没有 opt-in 开关,因此默认路径就是修复路径;关闭开关的并行行为不存在。剩余风险是远程 CI 未轮询、没有 live model/安装后桌面声明;这不影响已覆盖的浏览器与真实本地状态后端不变量。

我的整体评价

APPROVE。 该 exact head 完整解决了原 PR 的子树读取目标,并修复 reviewer 指出的来源/根联合历史恢复 P2。两个浏览器证据分别覆盖正向返回和旧逻辑负向敏感性;真实状态服务、打包 bundle 与本地 premerge 均通过。

Code volume 判定为 necessary:DashboardPage 的改动增加请求根隔离和正确的来源优先级,没有扩大状态机;测试新增主要来自第二个真实来源 fixture 和历史反例。唯一高价值简化已经在本次完成:复用 goalSubtree、workspaceReadPlan 与 request fence,而不是增加新的层级或请求框架。语义仍复用既有导航空间、来源和根词汇,没有创建共享新契约。

先前 review 的唯一 P2 已通过当前 head 的代码与反例验证解决。可以批准该 head,并进入独立的 merge-readiness 门禁。

English verdict: APPROVE - At d8c62a0, the restored source and root now drive one read intent; the prior A/research -> B/operations -> history.go(-4) failure fails on the old branch and passes in development plus three packaged runs. TypeScript, Chat bundle, progressive-loader smoke, 73 CORS tests, scoped dashboard tests, and 10/10 premerge canaries pass.

@huangruiteng

Copy link
Copy Markdown
Collaborator

Merging exact head d8c62a0bfa2004210a5deda3cf8a3ceb4747fbb2 under the maintainer-authorized self-merge/admin-bypass request.

  • Exact-head review and bilingual verdict are published and read back.
  • The prior P2 thread is resolved and the superseded review is dismissed.
  • Change-quality receipt cqr_f05f77ce0c9b895e644f is valid for scope f05f77ce0c9b895e644ff367c7625d9a7b80b89c6cce59ca56ec63425b8b8d25.
  • Premerge passed 10/10 catalog canaries plus diff, Python compile, RFC parity and public-boundary checks.
  • Development and 3/3 packaged browser runs passed; the old source-first logic failed the new history regression.
  • No unresolved code finding remains.

The LoopX readiness readback separately reports ready=false only for github_review_decision_not_approved: branch protection requires an approval from an eligible reviewer other than the last pusher, and the outstanding code-owner reviewer request is maxliux5. Remote CI was not polled under the Goal's wait_for_ci=false policy. This admin bypass is limited to that GitHub review requirement; it does not replace the exact-head review, validation, change-quality receipt or resolved-thread evidence above.

@huangruiteng
huangruiteng merged commit 3a81641 into main Oct 19, 2026
10 of 12 checks passed
@huangruiteng
huangruiteng deleted the codex/goal-subtree-read-scope-20261019 branch October 19, 2026 09:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants