Repository navigation
fix(manager): recover non-SSH steward turns on concurrent registry sync and skip no-op global registry writes - #7879
Conversation
…nc and skip no-op global registry writes Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
1) 问题定义(为什么改、要解决什么)
- 修复两项导致非 SSH Steward 会话(如 Antigravity 宿主管理会话)在并发后台同步时误报
external_authorization_changed的根因:(1)collect_manager_turn_context将所有remote_evidence=True调用一刀切禁止单次重试(即便当前会话并无 SSH 远程授权拨号开销),且未容忍bound_steward在单次采集中间新增受管 Goal 的单调扩张;(2)global_registry.py在实质内容未变时每次同步均重写synced_at与updated_at时间戳,高频触发注册表文件变更。
2) 改动摘要(按模块分组)
- 非 SSH Steward 并发重试与单调授权恢复 (
loopx/chat_manager_context.py):仅当remote_evidence且存在真实 SSHgrants时才置inline_remote = True;允许无 SSH 拨号的bound_steward在采样期间遇到单调 Goal 集合扩张或无实质变更的元数据刷新时重采样一次。 - 全局注册表幂等时间戳保持 (
loopx/global_registry.py):在merge_goal_entries与_merge_global_registry_payload中,若除synced_at/updated_at外的键值完全一致,则保留既有时间戳并跳过落盘重写。 - 回归测试 (
tests/test_chat_manager_context.py,tests/test_global_registry_write_serialization.py):新增并发注册表元数据触碰与新增 Goal 的重采样恢复测试,以及冗余同步不改写全局注册表文件(inode/mtime 不变)的回归测试。
3) 逐点审阅结论(架构、语义、正确性、测试、风险)
- 安全与权限边界:
set(before).issubset(set(after))且原before集合在新注册表下的manager_authorization_scope_id_for_registry严格等于before_scope_id时才允许重采样一次,既消除了后台refresh-state引发的误报,又严格保留了权限撤销(revocation)时的立即拒绝保证。
4) 建议修改项(分 P0/P1/P2,带文件路径和行号)
- 无阻塞项。
5) 最终结论(Verdict)
Approved — ready to merge once CI completes (exact head 57631282a9dd2fab2477a628b8566f6901589e6b verified).
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=max
动机 / Motivation
通过非 Codex endpoint 使用已绑定 Steward,恰逢后台刷新或注册新 Goal 的用户。 原来同一公开并发 fixture 在 registry metadata touch 或新增已授权 Goal 后都失去上下文;当前 head 在无 SSH 读取时重读一次并返回当前授权证据,同时保持真正 scope/instance 变化的拒绝。 同一实际 collector/registry 入口的三项回归在 base 均失败、head 均通过;六项追加反例证明换实例、撤销、加 Goal 同时换旧实例、持续抖动和非绑定范围新增均拒绝。66 + 92 个相关测试通过。 不增加 Steward 权限、自动恢复模型工作或跨主机一致性保证;不以读上下文成功推断已完成业务工作。
改动思路 / Approach
实际远端预算属于已有 SSH evidence owner,不能由调用者的 remote_evidence 意图代替。重试仍在当前 collector 内并复用原 scope fingerprint;时间戳抑制复用已持锁的 registry reducer 和实际 payload 相等判断。 两个既有生产边界的局部修复:有界上下文重收集和无变化 global registry 同步。没有新 provider、状态或 frontend 流程。 只增加无条件重试会重复 SSH 拨号并放宽权限竞态;只去掉时间戳也不能恢复真实新 Goal 注册。当前一组修复在同一已证实并发触发链上收口,不引入调度器、缓存或新的重试 framework。 正向路径从 collect_manager_turn_context / sync_project_registry_to_global 进入既有 owner,metadata touch 与新 Goal 两例均恢复;另一个真实 registry 例在 no-op 保持字节,随后改变 objective 仍发生真实写入;异常路径保留 既有 scope resolver/typed source grant 与 external_authorization_changed,六项反例按预期拒绝;既有 inline SSH suite 断言仅一次 dial/collection,权限拒绝不进入模型。本次完整审查当前 head,没有继承先前模板审查的通过结论。
具体改动 / Changes
4 paths +155/-3;两个生产文件的小分支调整,两份现有测试增加竞态/无变化写入回归并同步既有 endpoint 错误文案。 Core/source grants 决定谁能看哪些 Goal;collector 只重读合法当前来源,SSH owner 保留拨号预算;global registry reducer 判断实际内容变化。 无新 schema、闭集状态、Actor 生命周期或授权 vocabulary;时间戳仍为既有 observation 字段,不创建第二份 progress。
关键代码讲解
-
collect_manager_turn_context:收集并校验一次 manager Turn 的当前证据。 实际 grants 才占用 inline remote budget;绑定 Steward 可在旧身份不变时按当前授权重收集一次。 输入为当前 session、scope resolver、registry/runtime,关键条件是最多两次本地收集;旧 Goal 必须全部存在且身份 fingerprint 不变;SSH 不重复读取。scope/instance 改变和第二次竞态返回 external_authorization_changed -
merge_goal_entries:合并已有 Goal 路由与内容。 除 synced_at 外完全相同才保留旧 synced_at。 输入为existing and incoming Goal entries,关键条件是真正 objective/route/metadata 改变仍产生不同 payload;collision/replacement 规则保持。原冲突与写入拒绝不变 -
_merge_global_registry_payload:组装持锁 reducer 的目标 payload。 除 updated_at 外完全相同才保留原 updated_at,既有写入比较才可判断 no-op。 输入为完整 existing/merged project/Goal payload,关键条件是不丢 concurrent update,不把内容变更当作时间戳变化。原 writer/locking recovery owner
规格为 docs/reference/protocols/manager-evidence-and-continuity-v0.md,spec_revision 867615e2c3aa2a32516a71d46f11a20d6fcb3289;Global conversation initialization、A bounded portfolio with explicit coverage 和 One service, scoped continuity 要求读取当前授权来源、声明覆盖,并在来源竞态中有界恢复而不扩大权限。
- Global conversation initialization:implemented。每轮从当前授权 registry 读取并绑定正确 Goal 身份;chat prose 不成为 inventory。 对应
collect_manager_turn_context;base/head concurrent registry probe + six scope cases。 - A bounded portfolio with explicit coverage:implemented。来源变化可有界重读,真正权限/身份变化不能被假装为普通 metadata。 对应
manager_authorization_scope_id_for_registry;replacement/revocation/continued-churn cases。 - One service, scoped continuity:implemented。复用原 scope/authority;远端 budget 不因本地重收集而重新开始。 对应
inline_remote guard + existing SSH evidence owner;66 manager/registry tests including inline SSH single-dial refusal。
对主干的风险 / Risks
最强风险是为了修复过度阻塞而把真实撤销/换实例当成 metadata,或在重复收集时重置远端拨号预算。 为区分修复与无效测试,我对不可变 base/head 执行相同输入:base 三项实际回归均失败:本地竞态不恢复、无变化同步仍写入;head 三项均通过;六项 scope 对照符合预期,66 + 92 tests passed。仅验证新增 Goal 的 happy path 会漏掉身份替换。我额外执行了新增+替换、撤销、非绑定新增和持续抖动;它们均不能借这个恢复分支取得上下文。 全局 all_registered 覆盖来自既有绑定/source-grant owner;我同时验证非绑定新增不扩权,旧 instance 可变字段不能逃逸,正常授权新增重读后得到实际可用证据。
- PASSED:
same current production regression cases base/head— PASS:base3失败、head3通过 - PASSED:
66 manager/registry tests + six extra scope cases— PASS:相反方向拒绝保持 - PASSED:
pytest tests/test_native_steward_turn_host.py tests/test_antigravity_steward_adapter.py— PASS:92 passed,synthetic native peers/transport,不调用真实模型 - PASSED:
python -m py_compile loopx/chat_manager_context.py loopx/global_registry.py; full changed-line review— PASS:编译通过;whitespace 诊断单独披露为非阻塞清理
语义与 CI 对齐
沿用现有 typed conversation_scope/source_grants 与 canonical identity。没有用 prose substring 判断权限,没有新增 claimed lifecycle。 无 SSH grants、非绑定 scope、缺失/撤销 source 和已配置 SSH 的反方向均验证;未配置远端时不启动 SSH,原 restricted profile 与 authority 默认不变。 独立证据覆盖当前 changed invariant;远端 CI 的其他状态与合并 readiness 另行保留,没有把检查颜色当作正确性证明。 source collector、typed runtime、真实临时 registry 与宿主测试路径已执行;测试通过注入一次并发写制造竞态,SSH 只用失败 runner,未访问用户主机或真实模型。未宣称安装 App 或真实外网 SSH 资格。 非阻塞清理:git diff --check 报告两份新增测试末尾多余空行,建议顺手清理;没有把该诊断记为通过,合并 readiness 仍应独立执行。
我的整体评价 / Verdict
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
APPROVE:当前完整 diff 在所声明边界内达到 goal_achieved;long_horizon 为 preserved,保留真实权限、scope 和单次远端预算;本地竞态可恢复,减少一次人为重问,但没有声称长程任务效果或端到端性能已经测量。 user_experience 为 improved,零 SSH grant 的授权 Steward 可从常见刷新竞态恢复到可用上下文,旧身份/撤销等错误继续明确拒绝。 检查了既有 Steward prepare-turn 与 Chat 错误回读 owner;未改 UI 布局/入口/设置,visual_review not_applicable。 实际远端预算属于已有 SSH evidence owner,不能由调用者的 remote_evidence 意图代替。重试仍在当前 collector 内并复用原 scope fingerprint;时间戳抑制复用已持锁的 registry reducer 和实际 payload 相等判断。 两个既有生产边界的局部修复:有界上下文重收集和无变化 global registry 同步。没有新 provider、状态或 frontend 流程。 已把意图布尔值改为实际 grants 判断,并在既有 reducer 消除无内容写入;没有增加重复恢复 helper。更广泛 registry/host 重构不属于当前竞态修复。 不把本次 review 当作合并授权。
English verdict: APPROVE — 57631282a9dd2fab2477a628b8566f6901589e6b; the bounded local retry recovers harmless registry races without relaxing identity, revocation or SSH-budget checks; unchanged registry syncs preserve bytes. 66 manager/registry tests + 92 native-host/Antigravity tests + base/head race probes + six scope counterfactuals pass.
Summary
manager_authorization_unavailable(当前连接的授权范围不可用) on non-Codex Steward turns:collect_manager_turn_context(loopx/chat_manager_context.py),prepare_turn_contextpassesremote_evidence=Truefor non-CodexAppServerAdapterSteward endpoints (such asAntigravityAdapter/antigravity_ls). Even when the Steward channel had zero configured SSH remote sources (grants(runtime_root, channel_id) == []),not remote_evidenceblocked theattempt == 1local recollection wheneverregistry.global.jsonwas touched mid-turn by a background worker. Gate the retry guard on actual configured SSH grants (inline_remote = bool(remote_evidence and grants(...))), and allowbound_steward(all_registered) turns to recollect once when a new Goal is registered mid-collection without mutating existing Goal instance identities.merge_goal_entriesand_merge_global_registry_payload(loopx/global_registry.py), preservesynced_atandupdated_atwhen a routineloopx refresh-statesync does not change any Goal or Project entry, preventing redundantregistry.global.jsondisk rewrites andsha256revision churn across concurrent workers.test_trusted_manager_profile_rejects_endpoint_that_cannot_enforce_itintests/test_chat_manager_context.pyto match theCodex or Antigravityerror message from feat(chat): support Antigravity (Gemini 4 Argon) as Steward and Chat executor #7832.Verification
pytest tests/test_chat_manager_context.py tests/test_global_registry_write_serialization.py tests/test_native_steward_turn_host.py tests/test_antigravity_steward_adapter.py