Repository navigation
Preserve material ranking lineage across lifecycle changes - #7002
huangruiteng wants to merge 1 commit into
Conversation
Signed-off-by: huangruiteng <huangrt01@163.com>
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
动机
需要维护受管素材排名,并在归档后继续生成可读队列的源适配器作者与操作者。
例如队列恰有三条、活动窗口为三条,归档其中一条后应保留其余两条及原相对顺序;本 head 却拒绝生成新的排名回执。
预期结果是只修正派生计数、只移除指定归档成员,并用正确归属的新修订继续投影;常规三条到两条且窗口为一的路径已通过,窗口收缩和来源归属仍未满足。
本评审不把 SDK 回执生成视为实际源写入、Core 授权、用户安装或完整前端素材管理流程的证明,也不要求在此 PR 引入私有文件解析器。
作者要修复的是归档后派生计数与实际素材集合脱节的问题。我按不可变基线 973914e8cb713ee176737c95750aedb3072b9ea8 与 head 2a4edc1f08143c639386276aa7aff33d0559cbea 审查;没有以本 PR 新写的约束作为唯一验收来源。
改动思路
在既有 Material Lifecycle SDK 内补充派生计数和归档 lineage 是有界的扩展;源解析与写入仍归原适配器,不需要另建控制面或 provider。
当前可评审边界是公开 SDK 的计数、来源绑定和归档回执契约;修复下述两个正确性问题后再由现有源适配器承担持久化、授权校验及可读队列的采用验收。
原来源适配器提供完整成员与已知 owner,通用 SDK 只派生数量、验证只移除一个目标,再生成新修订的审计回执。操作者不应该为了修复计数重新说明已知归属,也不应该被要求造一个占位素材来让队列保持非空。保持这一边界比增加新的通用写入器、控制面或人工确认更合适。
以下是针对 2a4edc1f08143c639386276aa7aff33d0559cbea 的源码路径说明,属于解释性表格;实际执行证据在风险部分。
| 输入/触发 | 预期结果 | 当前观察 |
|---|---|---|
| 同 owner,三条、窗口一,归档中间成员 | 其余 entry 身份与顺序不变,新回执可选中 | 通过 |
| 完整的 A 来源包,目标 owner 改为 B | 拒绝来源不匹配 | 错误地产生 B 的 applied 回执 |
| 三条、窗口三,归档一条 | 两条真实成员与正确摘要 | 被固定窗口下限拒绝 |
| 最后一条归档 | 空集合、零计数、有效 lineage | 被非空约束拒绝 |
具体改动
发现两个阻塞问题,均已通过公开 SDK 独立复现:
- [P2] 在生成 applied 回执前核对来源归属。完整且摘要正确的来源包目前仍可换一个 owner 使用:用
goal:A、revision:41建立 repair plan,再以goal_id="goal:B"调用本函数,实际返回goal:B的status="applied"回执;改成另一个project_scope也成功。归档 builder 同样接受 Goal A 的 lifecycle receipt 并生成 Goal B 的排名回执。摘要只证明包内容未变,不证明归属一致;不同集合复用局部 material/entry refs 时会留下错误的 applied lineage。请在两个 builder 中比较完整的规范化源/目标 owner(Goal 或 project/profile/grant),不匹配即拒绝,并补齐跨 Goal、Goal/project 与 project scope 不匹配的负例和同 owner 正例。这是回执完整性问题;本复现没有执行真实源写入或 Core 权限绕过。 - [P2] 允许归档后的排名窗口收缩或变空。此处把归档前的
top_window_size原样用于 after 集合,而 normalizer 同时要求非空且窗口不大于条目数。因此三条、窗口为三的合法集合移除一条后,会抛出top_window_size cannot exceed entry count;一条、窗口为一归档至[]则始终抛出entries must contain at least one entry。这正是正常归档逐步耗尽队列的路径,不能用改变旧窗口或伪造占位成员恢复。请保留配置窗口,分别按真实 before/after 集合推导活动条数(至多为窗口大小)、backlog 和零条摘要,同时继续拒绝重复/乱序/意外成员变更;至少补 30→29、3→2 与 1→0 的公开 SDK 用例。
规范映射到 docs/reference/protocols/material-lifecycle-architecture-v0.md @ 973914e8cb713ee176737c95750aedb3072b9ea8:ML-OWNER 是 Position 的唯一来源归属与现有 Core 授权边界,当前跨归属回执未满足;ML-ARCHIVE 是 Stage-0 的合法归档、稳定素材/归档引用及本次任务的后续排名投影,当前窗口收缩/耗尽未满足;ML-STAGE 是 Stage Boundary 的私有解析器/写入适配器保留边界,当前 SDK 没有扩大真实写入权限,符合该边界。上述 ML 标签是本评审对原段落的映射名称,不是新增协议字段。
关键代码讲解
inspect_material_ranked_state:按明确 rank 排序,验证唯一成员与连续名次,生成计数和顺序摘要。它只验证调用者提供的列表结构,不能凭complete_coverage_verified证明源存储没有漏项;固定正窗口与非空约束是第二个问题的根源。build_material_ranked_state_repair_apply_receipt:校验 plan 摘要、源修订、修订变化和调用者读回断言。随后独立写入目标 owner,却没有将其与 plan owner 比较,因此正确摘要也能被错误归属。build_material_lifecycle_ranking_apply_receipt:确认 lifecycle 包确实是 archive,并核对移除恰好一个目标、剩余 entry 身份和顺序。缺少来源 owner 比较;前后两次规范化复用同一窗口,导致正常结束状态先被拒绝。resolve_material_ranking_lineage_receipt:只按认可 schema、applied和相同after_revision选唯一结果,零个或多个都拒绝。它不替调用者解析 Core 授权,也不能替前面的生产者发现错误归属。
完整 diff 是 10 个文件、825 行新增:生产代码 507 行(主要为 461 行的 ranked_state.py),测试/公开 smoke 266 行,双语文档 52 行。复用了既有 ownership、摘要和 token 校验;没有引入新存储。既有 rebuild 允许结构分组变化,rerank 允许有界移动,二者均不能直接替代本次“只修计数/只删一个归档目标”的规则。最值得合并的局部改进是让两个 apply builder 共用来源归属比较,避免再次遗漏;无须重写整个 capability。
对主干的风险
当前 head 的 59 项仓库测试通过,公开 material-lifecycle-contract-smoke.py 返回 ok=true。原生 premerge 的 6 项直接检查及 15 项选择检查全部通过,包括编译、维护性、语义词汇、风险 smoke 与公共边界;没有失败或跳过,没有据此获得合并权限。原生检查使用的 origin/main 在执行时已前进,但与 head 的实际 merge base 是上述不可变基线,检查范围仍是本 PR 的 10 个文件。
我另外使用同一合成输入直接调用生产 SDK,加入十个独立不变量断言:5 通过、5 失败。失败具体为 repair_wrong_goal、repair_goal_to_project、archive_wrong_goal、archive_window_shrinks_3_to_2、archive_last_material。这五个失败来自新代码,不是外部环境或既有红测;原测试只覆盖了同归属、窗口一的非空成功路径。
基线上的四组原有测试共 54 项通过;同一固定时间、素材/修订输入生成的原 lifecycle/rerank 包与 head 完全一致。两次原生 material-lifecycle architecture 输出的差异只有明确新增的三个 schema 与三个 invariant。反向显示列表而不改 rank,repair plan 保持相同;正确摘要产生 repair_required=false;旧 repair 修订、剩余成员乱序与重复 lineage 仍被拒绝。没有为了兼容而归一化掉有意义差异。
最大风险是错误 owner 的回执看上去已经验证,投影可继续选择它;另一方向是正常队列越归档越无法继续。这些是公开包的完整性与继续工作问题,本复现没有触碰真实素材库、执行数据库写入或证明 Core 授权可绕过。原来的源适配器 CAS、实际读回、安装采用和完整前端素材管理任务没有经过本次 SDK 验证,不能由 authority_readback_verified=True 代替。source_revision 与排名 authority revision 是否属于同一命名空间未被既有契约完全说明,因此未把该观察另列为缺陷。
语义与 CI 对齐
新增状态使用准确 schema/status/owner 字段,没有按标题、子串或文案猜测。能力仍为 material-lifecycle、builtin、loopx-core,默认关闭;导入和发现不触发授权、写入、调度或配额。catalog 新增义务是明确披露的 capability 语义,原源适配器与安装边界保留。修复应继续采用类型化归属/成员规则,而非字符串启发式。当前管理策略 wait_for_ci=false,未查询、轮询或等待 GitHub CI。
我的整体评价
REQUEST_CHANGES。问题与现有 capability 的职责匹配,代码规模和新增契约可以作为有界 SDK 前置能力评审;但不能把正常路径的回执成功称为归档问题已修复。请先修复来源归属与队列耗尽两个边界,并同时保留合法同 owner、乱序/重复拒绝及基线兼容测试。后续源适配器采用应复用现有任务和真实读回,不需要为本评审增加另一套调度或通用写入框架。
在既有 Material Lifecycle SDK 内补充派生计数和归档 lineage 是有界的扩展;源解析与写入仍归原适配器,不需要另建控制面或 provider。 当前可评审边界是公开 SDK 的计数、来源绑定和归档回执契约;修复下述两个正确性问题后再由现有源适配器承担持久化、授权校验及可读队列的采用验收。
此结论不撤销默认关闭,也不替源 owner 授权。测试通过与静态协议扩展不能证明安装后的用户流程、持久化 CAS 或长程效果。修复后应按新的 exact head 重查本次全部受影响路径;本评审没有实施合并、安装或生产数据变更。
English verdict: REQUEST_CHANGES — exact head 2a4edc1f08143c639386276aa7aff33d0559cbea. Two P2 blockers are independently reproduced through the public SDK: applied repair/archive receipts can be rebound to another source owner, and legal archives fail when the Top-N window underfills or the ranked set becomes empty. Repository tests (59), native premerge (6 direct + 15 selected) and the normal contract smoke pass; five of ten independent invariant assertions fail. Existing SDK outputs match immutable base 973914e8cb713ee176737c95750aedb3072b9ea8. Keep this as an SDK-stage proposal, bind source ownership, admit truthful terminal counts, then rerun the positive and opposite-direction cases. No actual source-write or Core permission bypass is claimed.
| ) | ||
| receipt: dict[str, Any] = { | ||
| "schema_version": MATERIAL_RANKED_STATE_REPAIR_APPLY_RECEIPT_SCHEMA_VERSION, | ||
| **material_owner_fields(goal_id=goal_id, project_scope=project_scope), |
There was a problem hiding this comment.
[P2] 在生成 applied 回执前核对来源归属
完整且摘要正确的来源包目前仍可换一个 owner 使用:用 goal:A、revision:41 建立 repair plan,再以 goal_id="goal:B" 调用本函数,实际返回 goal:B 的 status="applied" 回执;改成另一个 project_scope 也成功。归档 builder 同样接受 Goal A 的 lifecycle receipt 并生成 Goal B 的排名回执。摘要只证明包内容未变,不证明归属一致;不同集合复用局部 material/entry refs 时会留下错误的 applied lineage。请在两个 builder 中比较完整的规范化源/目标 owner(Goal 或 project/profile/grant),不匹配即拒绝,并补齐跨 Goal、Goal/project 与 project scope 不匹配的负例和同 owner 正例。这是回执完整性问题;本复现没有执行真实源写入或 Core 权限绕过。
| ) | ||
| after_state = _normalize_ranked_entries( | ||
| after_entries, | ||
| top_window_size=top_window_size, |
There was a problem hiding this comment.
[P2] 允许归档后的排名窗口收缩或变空
此处把归档前的 top_window_size 原样用于 after 集合,而 normalizer 同时要求非空且窗口不大于条目数。因此三条、窗口为三的合法集合移除一条后,会抛出 top_window_size cannot exceed entry count;一条、窗口为一归档至 [] 则始终抛出 entries must contain at least one entry。这正是正常归档逐步耗尽队列的路径,不能用改变旧窗口或伪造占位成员恢复。请保留配置窗口,分别按真实 before/after 集合推导活动条数(至多为窗口大小)、backlog 和零条摘要,同时继续拒绝重复/乱序/意外成员变更;至少补 30→29、3→2 与 1→0 的公开 SDK 用例。
A lifecycle change can advance a managed material authority revision while its readable projection still points at a rebuild or Git-reconciliation receipt for an older revision. Stale or missing derived ranking counts can also block the next otherwise valid archive transition.
This adds provider-neutral ranked-state contracts that:
after_revision.The packets contain counts, digests, opaque refs, revisions, and verification flags only. Project-specific catalog parsing, source content, and private paths remain in adapters.
Validation:
uv run --extra test pytest -q tests/capabilities/test_material_ranked_state.py tests/capabilities/test_material_lifecycle_contracts.py tests/capabilities/test_material_readable_projection.py(35 passed)uv run python examples/material-lifecycle-contract-smoke.pyuv run --extra test ruff check ...ranked_state.pyuv run loopx canary premerge --from-git-diff(15/15 selected checks passed)