Skip to content

Preserve material ranking lineage across lifecycle changes - #7002

Open
huangruiteng wants to merge 1 commit into
mainfrom
codex/material-lifecycle-transition-projection
Open

huangruiteng wants to merge 1 commit into
mainfrom
codex/material-lifecycle-transition-projection

Conversation

@huangruiteng

Copy link
Copy Markdown
Collaborator

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:

  • derive and repair summary counts without changing ranked membership or order;
  • verify that archiving a ranked material removes only that material and preserves every remaining entry identity and relative order;
  • bind both repairs and archive-driven ranking changes to the resulting authority revision;
  • resolve exactly one applied ranking-lineage receipt by 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.py
  • uv run --extra test ruff check ...
  • isolated mypy check for ranked_state.py
  • uv run loopx canary premerge --from-git-diff (15/15 selected checks passed)

Signed-off-by: huangruiteng <huangrt01@163.com>

@loopx-agent loopx-agent 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-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 独立复现:

  1. [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 权限绕过。
  2. [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),

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.

[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,

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.

[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 用例。

This branch has not been deployed

No deployments
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