Skip to content

fix(collaboration): reject ambiguous original Turn provenance - #8210

Closed
loopx-agent wants to merge 4 commits into
mainfrom
codex/delegation-recovery-20261106-184859
Closed

loopx-agent wants to merge 4 commits into
mainfrom
codex/delegation-recovery-20261106-184859

Conversation

@loopx-agent

@loopx-agent loopx-agent commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

Problem And Result

委派列表曾在两个历史 Turn 共用 client_turn_id 时,按目录顺序选择一个来源并报告完整覆盖;错误的 Session、Turn 或 schema 身份也能通过读取。现在无法唯一证明的来源会保留为读取缺口,健康请求仍可查询;恢复同一个原始来源后,请求重新出现。

The same 61-request fixture changes from a falsely complete 61 to 60+ with one unverified record, then returns to 61 after exact source recovery. This proposal fixes that bounded provenance defect; it does not repair historical records or declare the wider delegation workflow complete.

Implementation And Ownership

  • original_turn_reader retains the existing per-read path index, marks duplicate client identities unavailable, and rereads the existing schema, Session, Turn filename and client identity before returning a Turn.
  • The existing File/SQLite HTTP regression and TypeScript packaged-browser journey cover invalid-source withholding, healthy neighbors, unchanged records and exact recovery.
  • One required-validation repair removes an unused assignment in the existing lost-response Goal-creation fixture. The operation still commits before the deliberate TimeoutError; recovery and single-dispatch assertions remain intact.
  • Capability: existing manager-context builtin / loopx-core. Python remains the physical Chat-filesystem provenance adapter. Shared TypeScript permission, lifecycle, agenda and return authority remain the owners. No new schema, setting, cache, module or execution permission.

The bounded refactoring pass retains the small reader/index and removes the unused fixture binding. A language migration or additional wrapper would enlarge this repair without improving its owning contract.

Author And Specification

Written by a model agent, Codex / OpenAI. The existing original-source, read-only inventory and original-conversation contracts in manager-context, together with the reproduced defect above, define this repair.

Acceptance Implementation Decisive evidence
Original evidence is uniquely attributable to the retained source original_turn_reader Duplicate client, foreign Session, foreign Turn and unsupported schema × real File/SQLite HTTP
Unknown sources do not hide healthy neighbors or become success Existing inventory coverage owner Unfinished/history/search reads, then exact restoration
Reading preserves source and coordination records Existing source reader and shared typed authority Byte-preservation assertions; zero inventory writes or new model Turns in both packaged journeys
Required local validation is executable Existing lost-response fixture Required Ruff passes; complete Goal-creation module preserves lost-response/single-dispatch checks

Validation

Tested clean head: 71b247644c60768afaa46c1fef29a1b299bc9d5b, integrated target main 9d276f6cb903ac1bbd465bf60ad4f04b295c5c43. Five-path proposal; all contribution and integration commits carry DCO sign-off. Run state: finished. Input classes: synthetic, public fixture. No live model, installation, release or merge.

Check Result Scope and limits
Real backend / integration PASS 222 tests, 359.85 s: complete test_chat_request_inventory.py, test_native_steward_ingress.py, test_manager_context_roundtrip.py, test_goal_create_completion.py; disposable File/SQLite and native-host fixtures
Packaged entrypoint PASS npm run smoke:delegation-cancel --prefix apps/presentation/dashboard, both 61-request backends; pagination, cancellation, late search, source fault/recovery, complete 15,620-character original body and returned-answer reading
Read-side effects PASS within fixture boundary Both packaged journeys: zero inventory writes and new model Turns. Existing reader Session resumes remain visible (4 File / 5 SQLite); this is not a claim of zero HTTP POSTs
Static / build PASS Required repository Ruff and all four changed Python files; configured strict mypy 17 files; complete control-plane TypeScript typecheck; shared conversation-return tests; Dashboard build and verified Chat bundle. Bundle size warning retained
Strict quality PASS / valid cqr_556d65378f05ac69c9c8, exact five-path fingerprint 556d65378f05ac69c9c8834dc4196cb69c63c959bdcad4f7997704106141a732; one bounded safe-fix pass; the optional typecheck failure below remains recorded
Standard premerge PASS 6 direct + 13 selected checks, no failures or manual holds; current strict quality enforced
Public boundary PASS Five changed paths, zero errors/warnings; whitespace, semantic vocabulary and bilingual RFC checks passed; no RFC changed in this proposal
Manual native-browser readback PASS within fixture boundary Verified Chat assets and real disposable SQLite HTTP: 12/61 → 12/60+ with one unverified source → 12/61; original source restored; expanded brief and actual original messages readable. Desktop 1440 and narrow 390, no horizontal overflow. Two ordinary Session-resume POSTs; no new Turn or model call
Full repository / live providers NOT RUN No full pytest, live PostgreSQL/Lark/model, physical IME or installed-App qualification. No authority-store migration or external transport change in this slice

Retained failures and matched attribution

The earlier head e1d29c2 retained a required Ruff F841 failure and a truncated exploratory mypy run reporting 4,557 errors. That failed receipt and those observations remain historical failures. The unused fixture assignment is now repaired; required Ruff passes.

The optional command uv run --extra test python -m mypy loopx/control_plane/collaboration/source_chat_observation.py still fails. Fresh independent runs at the immutable current base and head each report 4,576 errors in 463 files. Their complete 4,731-line outputs agree in order after location-only normalization; command, configuration/fixture and environment digests match. The only raw difference is the same unchanged untyped trusted_source_chat_root function moving from line 76 to 83. No import edge changed. This is exact-scope pre_existing_unrelated attribution for an optional check, supported by the passed required invariants over all five changed paths. It is not a full-tree typecheck pass or a waiver of required validation.

Historical CI on e1d29c2 also showed the same Ruff failure and an advisory Ark job referencing a missing test file. New-head CI is unobserved under the configured no-CI-wait policy; none of the old failed/skipped jobs is relabeled green. Exact-head review, independent review and maintainer merge authorization remain separate.

Frontend Evidence

Public synthetic billing-export requests over the real disposable backend. The UI layout is unchanged. The primary question remains which delegation owes a result and whether its sources can be verified. Grouped rows, explicit loaded/total counts and existing one-click uncertainty help remain central; complete briefs, source messages and provenance remain available on demand. The partial-coverage help is generic; it does not identify the exact corruption cause.

Current-head desktop, 1440×1000: healthy rows remain readable and one unverified source is disclosed.

Current head: partial original-source coverage, desktop

Current-head narrow expanded brief, 390×844: readable constraints and acceptance, retained partial count and pagination.

Current head: expanded brief at 390px

Current-head exact source recovery, 390×844: 61 records and the uncertainty marker removed.

Current head: original source restored

Original matched before/after evidence, retained with its revision boundary

The original baseline reader and the same built frontend falsely count an ambiguous source as complete:

Before: falsely complete 61 records

Original proposal e1d29c2, desktop/narrow partial coverage and recovery. The production reader hunk is unchanged in the current head; the current-head journey above revalidates it with integrated main.

Original proposal: one unverified source, desktop

Original proposal: one unverified source, narrow

Original proposal: source restored

Remaining Scope And Rollback

Existing historical receipt conflicts still require original sending/correction evidence; this patch neither rewrites them nor infers cancellation. Slow-read attribution, full multi-host permissions, installed Mac/WebView/Lark behavior and requester adoption remain with the existing delegation work. This PR makes no latency, complete-cost or long-horizon benefit claim.

Exact source restoration is the runtime recovery path. Reverting the proposal removes this bounded reader protection and its regressions; the test-only unused-binding cleanup can be reverted separately. No merge, installation, release, provider execution or new protected-operation authority is part of this proposal.

Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewer: actor_kind=model_agent; model=gpt-6.1-sol; provider=OpenAI; declaration_source=runtime_reported; reasoning_effort=max.

Approval conclusion (author-owned PR; GitHub blocks formal self-approval)

动机 / Motivation

在管家入口查看待跟进委派的用户,会在原始来源损坏或重复时得到误导的完整计数。 同一组61条合成交办,旧读取器把一个重复来源也算作已核验;新读取器显示60+与1条未核验,恢复原始来源后重新显示61。 这会让用户误以为交办已完整核验,并把后续跟进建立在未证明的来源上。其他健康请求仍可展开,坏来源不泄露正文;同一原始来源恢复后重新出现,读取不修改来源或协调记录。 本PR不修复历史回执、不判定取消、不新增执行权限,也不宣称首次读取提速或完整委派流程完成。 历史回执冲突的原始发送证据、慢读归因、跨宿主权限、安装态行为和请求方实际采用仍待原流程验证。

改动思路 / Approach

读取入口仍是已有的 GET /api/chat/request-inventory。实际 Chat 宿主提供 Session 和保留的 Turn 文件,Python 只定位并观察物理证据;来源权限、Goal 生命周期与回执状态仍由共享 typed owner 决定。正常来源通过后进入原有列表,失败来源进入已有的未核验计数。每次刷新重新建立路径索引,恢复原始文件后可以前进;读取本身不会修历史记录。

来源唯一性属于现有物理证据读取适配器;权限和生命周期继续由共享TypeScript owner决定,新增第二套状态或迁移整条链都不能改善这次局部不变量。 当前PR只修复原始Turn证据定位、沿用已有不完整覆盖投影并补全故障恢复验证;额外一行夹具清理解除必需Ruff失败。 observe_source_chat 是另一条单来源转发观察路径,不能直接代替这里的批量定位与逐请求授权检查。目录排序后继续选一个来源仍然无法证明唯一性;新增内容缓存、第二个状态 owner 或整条语言重写也没有必要。

具体改动 / Changes

复核范围为 9d276f6cb903ac1bbd465bf60ad4f04b295c5c43 到 71b247644c60768afaa46c1fef29a1b299bc9d5b 的完整五路径差异,85 行增加、4 行删除。其中生产适配器为10+/3−,其余是现有测试与合成夹具75+/1−,没有新增模块、协议、设置或公开 CLI。相关主干 #8220 已在冻结验证之前集成,未继承原提案的通过结论;远端评论、inline comments 和 reviews 当前为空。

关键代码讲解

  1. original_turn_reader 在一次读取里保留路径索引。旧逻辑第一次遇到 client_turn_id 就决定来源;现在重复 key 留为 None,包括第三个重复也不会重新变成可用,因此不依赖目录顺序。
  2. original_turn_reader.read 每次使用时重新读文件,必须同时匹配既有 schema、Session、文件名 Turn 身份和 client id,才返回正文。它没有缓存内容、恢复 Chat store 或授予权限;拒绝后由现有逐请求异常分支增加未核验数,健康 key 继续读取。
  3. test_unproved_original_turn_is_withheld_without_hiding_other_requests 在真实 HTTP 与 File/SQLite 的一次性存储内注入四种来源错误,检查 unfinished/history/search 不泄露坏记录、健康邻居仍在、读取前后字节不变,随后恢复来源并核对两条请求都重现。
  4. lose_response 只去掉未使用的赋值。原方法仍先执行,再抛出模拟丢失响应的 TimeoutError;完整模块继续检查幂等恢复和一次派发,没有把必需 Ruff 失败降级。

规范引用:loopx/capabilities/manager_context/README.md,固定版本 9d276f6cb903ac1bbd465bf60ad4f04b295c5c43(同一文本)。以下 ID 是原文行锚点:manager-context:L267-L275 的不可读来源保持部分覆盖、manager-context:L278-L285 的逐请求来源/权限/回执核验、manager-context:L287-L296 的刷新原 owner 且不修回执、manager-context:L323-L341 的完整原 Turn 阅读,以及 manager-context:L343-L350 的只读 agenda,均由既有 owner 保持并在本次真实路径验证;未把未来安装态或采用效果提升成当前已交付事实。

同输入反证 当前主干 当前 PR
重复 client、外来 Session、外来 Turn、未知 schema × File/SQLite 8 个断言失败:错误来源仍被计入完整覆盖 8 个故障用例通过,邻居和恢复保留
正常内容重读、已读/采用后仍欠结论 4 个控制通过 同样4个通过

另补同输入的两种 provider 新来源用例:先读取1条,再新建并交办合法来源,刷新得到2条;主干与PR各2通过。该本地 harness 最初缺少 pytest fixture 注册的 setup 失败保留,补齐注册后执行,不涉及生产代码或断言变更。

这12个用例在各自 checkout 和独立环境执行相同测试源码,经过实际 HTTP handler;主干8失败4通过,PR12通过。完整四模块另外222通过(359.85秒);打包 smoke:delegation-cancel 在61条 File/SQLite 中覆盖分页、取消、晚到搜索、来源故障/恢复、15,620字符原文和返回答案。两者 inventory 写入和新模型 Turn 均为0,但保留4/5次普通 reader Session resume,不能说没有HTTP POST。

对主干的风险 / Risks

最强反例是:更严格的来源检查虽然挡住重复记录,却把同批健康请求或恢复后的记录一起隐藏。这次通过实际相邻请求身份、部分计数、后来有效 Turn、恢复后的完整集合与字节保全断言排除了该反例。生产调用在 conversation_inventory.py:190-200,并非仅测试可达。破损来源需要原 owner 恢复;UI 的通用未核验提示没有诊断具体损坏原因,这一局限保留。

必需 Ruff、配置内17文件严格 mypy、control-plane TS、Dashboard/verified Chat 构建、精确五路径 CQR cqr_556d65378f05ac69c9c8 和 premerge 的6项直接+13项选中检查均通过。可选直接文件 mypy 仍失败:当前主干/PR各4,576 errors /463 files,完整4,731行按顺序一致,只有同一个未改动函数的行号76→83;命令、配置/fixture和环境摘要匹配,无 import 改动。该失败按 pre_existing_unrelated 保留,不是全仓 typecheck通过或必需检查豁免。旧4,557错误截断记录、旧head的Ruff和advisory缺文件CI失败仍是历史失败;当前配置不等待CI,本轮没有查询新headCI。

这是现有能力始终生效的证据完整性修复,没有 opt-in/default-off 声明;有效来源的旧行为用上述控制对照保留,坏来源的默认行为收紧已公开。没有新 actor、权限、生命周期或公共语义词表;局部 Path | None 是物理证据推导值,不是手工同步的任务状态。通用规则没有引入领域词或把建议伪装成硬性工作义务。回退可独立撤销 reader hunk 与测试夹具清理;实际恢复路径是恢复同一原始来源再刷新。完整 pytest、真实PostgreSQL、live Lark/model、安装态Mac/WebView和物理IME未验证。

我的整体评价 / Verdict

交付判断是 justified_increment:一个既有读取 owner 内的来源正确性修复,机制成本与误导完整计数的风险相称。long_horizon=preserved,没有测出或宣称长期净收益和完整成本改善;user_experience=improved,坏来源的缺口能被看见且健康请求仍可用。既有 v1格式与当前调用方兼容,不增加兼容 wrapper;保留小索引并删除无用测试赋值,整链迁移留在有实际需求的边界。

视觉复核原因:已有部分覆盖投影的可见计数发生变化,不能只看安静列表。当前打包页面的61条内容、混合状态、展开正文、1440桌面及390窄屏已实际检查,公开截图在PR正文可加载,窄屏无横向溢出。未验证范围:安装态Mac/WebView、物理IME、live Lark、私有历史来源恢复和请求方实际采用。精确当前head未发现阻塞性问题;作者结论不等于独立review或maintainer merge授权,后续更改head需重审。历史回执冲突的原始发送证据、慢读归因、跨宿主权限、安装态行为和请求方实际采用仍待原流程验证。

English verdict: APPROVE this exact head for the bounded provenance repair; optional broad mypy remains failed, independent review and maintainer merge authorization remain separate.

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.

1 participant