Skip to content

fix(bootstrap): isolate objective prose from generated state grammar - #4418

Open
yilin-succeed wants to merge 3 commits into
loopx-project:mainfrom
yilin-succeed:codex/fix-objective-quote-isolation
Open

yilin-succeed wants to merge 3 commits into
loopx-project:mainfrom
yilin-succeed:codex/fix-objective-quote-isolation

Conversation

@yilin-succeed

Copy link
Copy Markdown
Contributor

Summary

Quote-isolate the Objective body at both state-writing boundaries so Markdown examples inside a goal objective can no longer open machine grammar and hide (or fake) the generated Todo sections. A fenced objective can currently bootstrap successfully while Todo readback returns no work.

  • render_objective_markdown (bootstrap) quotes each objective line into a blockquote; render_state_markdown and project-registration _state_markdown embed it under ## Objective. The frontmatter objective field keeps the raw text unchanged.
  • Generated-state grammar keeps full authority: no parser, visible_markdown_lines, region, authority, or transition changes. Quoted lines cannot open fences, comments, headings, or task lines, so both the Python and TypeScript readers stay protected by the writer boundary.
  • Chat context readback (_active_state_section) strips the quote prefix so the Objective round-trips verbatim; non-quoted legacy lines keep their existing bullet flattening.

Scope: the two objective writers, one chat section readback, and focused tests. No Todo parser, authority, scheduler, provider, or public CLI interface changes. Repairing already-malformed live goal state stays out of scope.

Issue Or Task

Addresses #4401. I noticed #4402 covers the same issue with a quote + HTML-escape + frontmatter JSON-serialization approach; this PR is an alternative, smaller-diff isolation variant (quote-only, no escape/decode roundtrip, frontmatter untouched) offered for maintainer comparison. If #4402 is preferred, feel free to close this one.

Validation

  • New regression tests/control_plane/test_objective_isolation.py (15 cases): inline and tilde fences, unclosed HTML comment, heading-like and task-like objective examples, plain-text and closed-multiline-fence controls, frontmatter fidelity, chat readback round-trip, and the project-registration writer.
    python3 -m pytest -q tests/control_plane/test_objective_isolation.py — 15 passed
  • Focused suites around both writers and the reader — all green:
    python3 -m pytest -q tests/cli_commands/test_project_registry.py tests/control_plane/test_todo_next_action_settlement.py tests/control_plane/test_goal_terminal_no_followup.py tests/control_plane/test_todo_document_ownership.py tests/control_plane/test_todo_projection_recovery.py tests/test_chat_completed_todos.py tests/test_chat_server_cors.py tests/test_agent_onboarding_unconnected_project.py tests/test_chat_image_attachments.py tests/test_global_registry_write_serialization.py tests/test_state_file_containment.py — 229 passed
  • ruff check on the changed files and python3 -m py_compile on the changed modules pass.

Signed-off-by: yilin-succeed 204474593+yilin-succeed@users.noreply.github.com

Quote-isolate the Objective body in both state writers so a collapsed or
unclosed fence, comment, or heading/task example inside an objective can
no longer open machine grammar and hide the generated Todo sections
below. The frontmatter objective keeps the raw text, and the chat
context readback strips the quote prefix to recover it verbatim.

References loopx-project#4401

Signed-off-by: yilin-succeed <204474593+yilin-succeed@users.noreply.github.com>

@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.

详细中文评审

审查对象:e15ee864ba625de1ae73d25324c9168d941a7bf4fix(bootstrap): isolate objective prose from generated state,base main,4 文件 +149/-8)。执行契约 policy_revision=3;结果 JSON 已通过 pr-review --check-result

动机

生成状态文件把自己的语法和运营方写的 objective 混在同一层:## Objective 段落直接写入原始文本。于是 objective 里只要出现未闭合或 start-goal-collapsed 的代码围栏、未闭合 HTML 注释、一个 ## Agent Todo 示例或一行待办形状的文本,就会打开机器语法,把其后的生成段落吞掉。症状很糟:Todo 源静默消失,没有任何报错。本 PR 让 objective 逐行进入引用块,使这些字符只能是内容。

改动思路

在两个 renderer(loopx/bootstrap.py::render_state_markdownloopx/control_plane/projects/registry.py::_state_markdown)里把 objective 逐行加 > ,并在 chat server 读回时把前缀剥掉,保证 frontmatter 与读回文本仍与原文一致。方向正确,边界也抓得准:围栏、注释、标题、待办形状四类都被覆盖。

具体改动

  • render_objective_markdownloopx/bootstrap.py:468):逐行引用,空行写 >;frontmatter 保持原始 objective 不做二次改写。
  • _state_markdownloopx/control_plane/projects/registry.py:145):注册路径复用同一渲染。
  • _active_state_sectionloopx/chat_server.py:132):新增 > 前缀剥离分支,legacy 未引用状态仍走原有 bullet 剥离。
  • 新增 tests/control_plane/test_objective_isolation.py(119 行,15 个用例):五类边界 objective × 「Todo 段落仍可解析」「读回与原文一致」两组断言。

对主干的风险

本 head 过不了仓库门禁。 在 head e15ee864b 的独立 worktree 实跑 tests/canary/test_maintainability_ratchet.py1 failed, 7 passed,报告 unreviewed finding: module_metric_budget:loopx/chat_server.py。原因很具体:loopx/chat_server.py 的已登记体积基线就是 1513 行(等于 main 当前值),本 PR 又给它加了 6 行(1519),于是变成一项未评审的控制面债务。远端对应 test-shard (4) fail,聚合作业 pytestmerge-gate 随之 fail。

第二项风险是归属:## Objective 段读取在仓库里已经有一个 owner(loopx/control_plane/goals/active_state_sections.py::active_state_sections,按 SECTION_HEADING_PATTERN 抽段,loopx/status.pyloopx/state_projection.py 都有同名模式)。本 PR 把新逻辑留成 chat server 的私有 helper,等于新增第二份段扫描实现。把读回搬到 goals 边界上下文可以一次解决体积与重复两项。

方向性风险:本 head 只做引用隔离,未覆盖 HTML 元字符与 U+0085/U+2028/U+2029 行分隔符;同一边界的另一份并行实现(#4402)覆盖了这两类。两 PR 目标是同一个洞,建议合并为一个节点交付,而不是让评审在两份实现间反复比对。

我的整体评价

REQUEST_CHANGES。修复方向正确、用例扎实(15 passed),change_proportionality=proportionateauthority_semantics=alignedobservable_semantics=intentional_change_validated;但 repository_reuse=unjustified_duplication 且存在未解决阻塞发现,两项都指向同一处修复。

最小修复路径(择一):

  1. 把 Objective 段读取从 loopx/chat_server.py 移出,复用/扩展 loopx/control_plane/goals/active_state_sections.py——同时清掉体积越界与重复实现;
  2. 若要与并行 PR 收敛,直接关闭本 PR并让 #4402 承担该修复(#4402 已把规则搬进 active_state_metadata,且 ratchet 在本 head 为绿)。

验证:head e15ee864b 独立 worktree 内 pytest tests/control_plane/test_objective_isolation.py -q → 15 passed;pytest tests/canary/test_maintainability_ratchet.py -q → 1 failed(chat_server 体积越界)。远端 checks:test-shard (4)/pytest/merge-gate fail,其余 pass 或 skipping。


English verdict: REQUEST_CHANGES at e15ee864ba625de1ae73d25324c9168d941a7bf4. The direction is right and its own 15 boundary tests pass, but the head cannot pass the repository gate: the canary maintainability ratchet reports an unreviewed module_metric_budget finding on loopx/chat_server.py (1519 lines vs the registered 1513-line ceiling, i.e. +6 lines in an already-ceilinged module), and the remote test-shard (4), pytest and merge-gate checks fail for that reason. The second finding is ownership: section extraction already has an owner in loopx/control_plane/goals/active_state_sections.py, so the new private scanner in chat_server.py is a duplicate. Minimum repair: move the Objective section readback into the goals bounded context (clears both the size ceiling and the duplication), or consolidate with the parallel same-boundary PR #4402 and close this one.

…ections

Move the chat Objective section readback out of chat_server into
loopx/control_plane/goals/active_state_sections.py, which already owns
active-state section extraction. The quote-prefix round-trip and legacy
bullet flattening move verbatim behind active_state_section_text, and
the chat helper stays as a delegating wrapper, so no behavior changes.

Clears the unreviewed module_metric_budget finding on chat_server.py
(1519 -> 1502 lines against the registered 1513 ceiling) and removes the
duplicated private section scanner.

Signed-off-by: yilin-succeed <204474593+yilin-succeed@users.noreply.github.com>
The pull_request synchronize event for 3b0e3ec did not register any
check suite on the base repository; push an empty commit to re-deliver
the event.

Signed-off-by: yilin-succeed <204474593+yilin-succeed@users.noreply.github.com>

@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.

动机

REQUEST_CHANGES。 审查 head:cd921c260eaff2dc691745b9558a5d554bb7d93e

这个 PR 要修的洞是真的:生成状态文件把机器语法和 operator 写的 objective 放在同一层,于是 objective 里一个未闭合代码围栏、未闭合 HTML 注释、一个 ## Agent Todo 示例或一行待办形状的文本就能打开机器语法、把后面的生成段落吞掉,而且静默无报错。方向、边界选择都对,上一轮 review 提出的两个问题(chat_server 体积越界、Objective 段读取的归属)在这个 head 里也都处理了。但现在的结论不是"还差一点",而是这个修复已经用更好的实现合进 main 了

改动思路

  • 写入侧:render_objective_markdownloopx/bootstrap.py:468)把 objective 逐行加 > registry._state_markdown 复用同一渲染。
  • 读取侧:上一轮之后作者把段读取从 chat_server 私有 helper 提取为 loopx/control_plane/goals/active_state_sections.py::active_state_section_textchat_server._active_state_section 改为委派(这是本 head 相对 e15ee864b 的唯一改动)。
  • 第三个提交是空提交 chore(ci): re-trigger pull request workflows,用于重新触发 CI。

具体改动

关键代码讲解

  1. render_objective_markdown"\n".join(f"> {line}" if line.strip() else ">" ...)——隔离语法,但不做转义
  2. active_state_section_text(state_text, heading, normalize_text=...)> 前缀剥离走引用路径,其余行走 legacy - 扁平化;注释行与空行跳过。
  3. tests/control_plane/test_objective_isolation.py:+119 行、6 个测试函数(含参数化共约 15 个用例)。

独立验证(exact head cd921c260e,worktree /private/tmp/pr4418-r20

  • pytest -q tests/canary/test_maintainability_ratchet.py tests/control_plane/test_objective_isolation.py23 passed。上一轮 review 的体积阻塞已解除:loopx/chat_server.py 现在 1502 行(基线是 1513)。
  • 但这个修复已经合入 main:并行 PR #4402fix(goals): isolate Objective presentation and unify legacy readback,作者 ehz0ah)已于 2026-09-15T12:19:37Z 合并,而本 head 是在 15:52:46Z 才推送的。main 现在:loopx/bootstrap.py:518markdown_blockquote(objective) 写入,loopx/control_plane/goals/active_state_metadata.py:37-38markdown_blockquotef"> {escape(line, quote=False)}",读取用 unescape(line[2:])
  • 我在 main 上跑了一个恶意 objective 探针:\``\n## Agent Todo\n- [ ] evil\n<!-- open comment→ 写出的行是'> '`、`'> ## Agent Todo'`、`'> - [ ] evil'`、`'> &lt;!-- open comment'`,读回得到 `' ## Agent Todo - [ ] evil <!-- open comment',而且紧随其后的生成段 ## Agent Todo仍能解析为['', '- [x] real', '']。也就是说 main 的实现是**同一规则 + 额外转义 + 同一回归覆盖**(tests/control_plane/test_goal_objective_projection.py 已覆盖 open-comment、Unicode 分隔符、## Agent Todo` 示例、围栏示例、长 objective 读回)。
  • 合并状态:mergeStateStatus=DIRTY,冲突文件正是 loopx/bootstrap.pyloopx/chat_server.pyloopx/control_plane/projects/registry.py 这三个 #4402 已经改过的文件。
  • 远端检查:该 head 是 fork PR(yilin-succeed/loopx),statusCheckRollup 为空、没有任何 check run,因此必需检查无法观测(不是绿色)。

对主干的风险

[P1,重复实现] 本 PR 与已合并的 #4402 争同一个边界,且实现更弱。 定位:loopx/bootstrap.py:468-481loopx/control_plane/goals/active_state_sections.py:30-57,对照 main 的 loopx/control_plane/goals/active_state_metadata.py:37-57。合入的后果是同一条规则出现两个同名读取器(active_state_metadata.active_state_section_textactive_state_sections.active_state_section_text)和两份写入路径,而仓库规则明确要求"一个规则一个 owner、compress rather than append"。最小修复:作为已合并变更关闭,或 rebase 后只保留 main 缺失的那部分(目前看不到这类内容)。

[P2,实现弱于 main] 引用隔离没有转义。 f"> {line}" 不处理 HTML 元字符与 U+0085/U+2028/U+2029 行分隔符;main 的 escape(line, quote=False) + 读回 unescape 覆盖了这两类(这正是上一轮 review 提到的并行实现差异)。若两者共存,同一输入会得到不同读回文本。最小修复:直接复用 active_state_metadata.markdown_blockquote,不要本地再写一份引用 helper。

主机侧状态(非代码结论):分支与 main 冲突(DIRTY),且 fork 的 CI 未运行(零 check run),所以"必需检查"在本 head 是不可观测而非通过;这本身不是代码缺陷,但意味着即便代码方向可取,本 head 也不具备可合并状态。

我的整体评价

修复思路正确、边界清楚,这一轮也确实把上一轮指出的体积与归属问题都处理干净了(ratchet 8 passed、隔离用例 15 passed)。问题在于时机与收敛:#4402 已经用更完整(带转义)的实现把这个洞在 main 上补掉了,并且带了同边界回归测试,本 head 现在既和 main 冲突,又会把一条规则拆成两个 owner。因此建议不要继续在这个分支上叠改动,而是以"已被 #4402 取代"关闭;如果作者认为 main 的实现仍缺某个具体用例(例如某个分隔符或读回场景),请只把这个用例作为最小增量提交,而不是重做写入/读取两侧。未批准、未合并。

English verdict: REQUEST_CHANGES at cd921c2. The direction is correct and this head fixes both earlier blockers (the maintainability ratchet is clean again - loopx/chat_server.py is 1502 lines, and the Objective readback now lives in loopx/control_plane/goals/active_state_sections.py instead of a chat_server-private helper; 23 local tests pass). But the fix already shipped on main: the parallel PR #4402 merged at 2026-09-15T12:19:37Z, before this head was pushed at 15:52:46Z, and main now writes the objective with markdown_blockquote (escape(line, quote=False)) and reads it back through active_state_metadata.active_state_section_text with unescape. An independent hostile-objective probe on main shows the escaped round-trip and the generated ## Agent Todo section still parsing, and main's own test file already covers open-comment, Unicode separators, ## Agent Todo examples, fenced examples and long readback. Landing this head would add a second same-named section reader and a quoting writer without escaping, and the branch conflicts with main on exactly the three files #4402 touched. Minimum repair: close as superseded, or rebase and keep only a delta main lacks. Required checks are unobservable for this head (fork PR, zero check runs), so repository_required_checks is recorded unverified rather than green. No merge performed.

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.

2 participants