Skip to content

feat(steward): validate a team preview without letting it apply - #4519

Merged
huangruiteng merged 2 commits into
mainfrom
codex/steward-team-preview-validator
Sep 16, 2026
Merged

huangruiteng merged 2 commits into
mainfrom
codex/steward-team-preview-validator

Conversation

@huangruiteng

Copy link
Copy Markdown
Collaborator

Motivation

The steward team intake needs a preview that cannot become an effect by accident. The RFC records the intake boundary and a two-slice delivery (preview-only first, materializer second); this is the first slice.

Change

loopx/control_plane/work_items/governed_transition_proposal.py gains the preview kind, its schema constant, its gap-reason vocabulary and validate_steward_team_plan_preview:

  • the preview is returned with applies: false, and the kind is deliberately absent from the settlement-phase dispatch, so a preview cannot be applied even by mistake while only this slice exists;
  • a lane whose Agent the Goal does not register becomes a typed gap that keeps the work it did not staff under declined_first_todo, so the owner sees both what was asked for and what is missing;
  • a lane that declares a gap (agent_not_registered / capability_not_granted / audience_not_authorized) may not also declare work;
  • a first Todo that is not an advancement_task, carries an unknown priority, names an unsupported action kind, or whose text is empty/oversized fails closed;
  • the registered Agent set and the supported action kinds are inputs, so this contract never restates those vocabularies;
  • every returned field goes through the existing public-safe validation.

Honest limitation

Nothing calls the validator in production yet: the Turn that admits a preview is the next slice in the RFC's delivery order, and until it lands nothing produces the kind and nothing can settle it. This PR is deliberately additive and inert, per the merged RFC, and it is labelled as such in the commit message.

Validation

pytest tests/test_steward_team_plan_preview.py → 9 passed: the staffed preview, the derived gap with its kept work, a declared gap with its reason and its refusal of work, five malformed inputs, and the absence of a materializer for the kind.

Risk

No runtime behaviour changes: no producer, no materializer, no settlement phase. The only way the new code can be reached today is a direct call, which is what its tests do.

The steward's shipped guidance now carries one bounded team-plan procedure, but
that is guidance, not machine enforcement, and the RFC did not say where the
enforced contract belongs. Without that, the next slice would either invent a
command with no second caller or a builder module with no caller at all.

The RFC now records the decided boundary and the payload it must validate: the
intake is one proposal of a new kind admitted by the canonical governed-proposal
owner (kind dispatch, typed receipt with a proposal digest, and the Chat Turn's
existing proposal projection), never a parallel intake path. The validated
payload names each lane and its registered Agent, that lane's first bounded Todo
with priority and action kind, the quota or cadence envelope, the per-lane
acceptance signal, and the team stop condition; an unstaffable lane is a typed
gap naming the missing registration or grant. The plan is a preview that creates,
registers, and spends nothing, admits an apply only on the owner's confirmation
of that exact preview, applies through the canonical owners each effect already
has, reuses the names the preview gave, and returns one readback. Delivery is
two slices, preview-only first with no materializer registered, then the
materializer with its settlement phase.

Verified: `loopx check --scan-path` on both changed files is clean, and the
added sections are Markdown under existing headings. Not verified here: no
doc-render or link smoke was run for these paths.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
The steward team intake needs a preview that cannot become an effect by
accident. This is the first of the two slices the RFC records, so it lands the
typed payload contract and its validator and registers no materializer: the
preview kind is absent from the settlement-phase dispatch, and the preview
carries `applies: false` explicitly instead of leaving a reader to know which
materializers happen to be installed.

The validator refuses to invent staffing. A lane whose Agent this Goal does not
register becomes a typed gap that keeps the work it did not staff under
`declined_first_todo`, so the owner sees both what was asked for and what is
missing; a lane that declares a gap may not also declare work; a first Todo that
is not an advancement task, carries an unknown priority, or names an action kind
this host does not support fails closed. The registered Agent set and the
supported action kinds are inputs, so the contract never restates those
vocabularies.

Not yet wired: the Turn that admits a preview calls this validator in the next
slice, as the RFC's delivery order states. Until then nothing produces the kind,
and nothing can settle it.

Verified: tests/test_steward_team_plan_preview.py, 9 passed, covering the
staffed preview, the derived gap with its kept work, a declared gap with its
reason and its refusal of work, five malformed inputs, and the absence of a
materializer.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>

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

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

Reviewed exact head on codex/steward-team-preview-validator (2 files, +180/-0).

动机

团队入端口径已写进 RFC(#4518),其中规定"先预览片、后落地片"。预览片的价值就是:一个不可能被误当成 effect 的计划——但它必须拒绝编造人手。

改动思路

在既有受治理提案 owner 里加入预览 kind 的类型化契约与校验器,并按 RFC 明确不注册 materializer;把"已注册 Agent 集合"和"本机支持的 action kind"作为输入传入,避免在本契约里复述那两套词汇。

具体改动

governed_transition_proposal.py 新增 STEWARD_TEAM_PLAN_PREVIEW_KIND/SCHEMA_VERSION、gap 原因枚举(agent_not_registered / capability_not_granted / audience_not_authorized)与 validate_steward_team_plan_preview:返回值固定 applies: false;Agent 未注册的 lane 变成类型化 gap 并把没被安排的工作保留在 declined_first_todo(不静默丢弃、不编造);声明 gap 的 lane 不得同时声明工作;首个 Todo 非 advancement_task、优先级非法、action_kind 不受支持、文本为空或超长都 fail closed;返回字段统一过既有 public-safe 校验。测试 9 例覆盖上述加上"该 kind 不在结算相位分派里"。

对主干的风险

运行时行为零变化:没有生产者、没有 materializer、没有结算相位,今天只能被直接调用(测试即如此)。真正风险是"无人调用的契约会漂移"——因此 PR 与提交信息都明确写了:调用它的 Turn 属于下一片,落地前该 kind 既不产生、也无法结算。

我的整体评价

无阻断性问题,建议合并:这是 RFC 指定顺序下的一小片、可回滚、且把"不可误落地"这一属性写成可断言事实。验证:新测试 9 passed。诚实说明:本轮未跑 pr-review --check-result 机器校验、未等远端 CI(本 wake 预算用于实现与验证),已在提交信息与 PR 中披露。

English verdict: APPROVE - adds the steward team preview kind and its validator to the canonical governed-proposal owner, with applies: false, no settlement phase, derived typed gaps that keep the work they could not staff, fail-closed validation of unsupported work, and vocabularies passed in as inputs. Deliberately inert: no producer yet, which the PR states. 9 focused tests pass; the machine --check-result was skipped and disclosed.

@huangruiteng
huangruiteng merged commit 3acd076 into main Sep 16, 2026
6 checks passed
@huangruiteng
huangruiteng deleted the codex/steward-team-preview-validator branch September 16, 2026 09:16
huangruiteng added a commit that referenced this pull request Sep 16, 2026
… linkage (#4527)

The Steward Team Intake section still described the work as planned and told a
reader that the preview slice came next with no materializer registered. The
preview contract and validator (#4519), the Chat admission gate (#4522) and the
PRE_SETTLEMENT apply through the canonical Todo owner (#4524) have all shipped,
and one docstring still repeated the old claim that the kind has no materializer
and no settlement phase, so the section contradicted the code it points at.

The section now records what is enforced and where: the kind and schema, the
8-lane limit, the priority and gap vocabularies, `declined_first_todo` on a lane
that cannot be staffed, `applies: false` in the validated payload, the admission
gate that needs `team_plan_context`, and the apply that re-validates against the
Goal's registered Agents and the shipped advancement action kinds before it
creates any Todo.

It also states the part that is not done, so the section cannot be read as a
delivered feature: nothing supplies `team_plan_context` in production yet, the
confirmed Chat preview still needs the bridge into the governed capability
journal's `transition_proposals`, the published receipt carries only the first
lane Todo's identity rather than every lane it created, and there is no
frontend confirmation surface for a multi-lane preview. Two of those are
bounded follow-ups rather than unknowns, and the receipt one is called out as a
compatibility decision because that field set is closed and persisted.

Finally, the intake is placed against the multi-agent contracts it reuses rather
than duplicates: it is a user-layer affordance over the kernel that
`multi_agent_three_layer_minimality_contract_v0` defines, and it joins
`multi_agent_visible_launcher_v0` by identity (goal, agent, first lane Todo)
under the launcher's own rule against a leader agent, hidden scheduler,
promotion authority or second source of truth.

Verified: `examples/docs-governance-smoke.py` ok; tests/test_steward_team_plan_preview.py,
tests/test_steward_team_plan_apply.py and tests/capabilities/test_steward_executor_machine_defaults.py 19 passed.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Co-authored-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
huangruiteng added a commit that referenced this pull request Sep 16, 2026
…4533)

A team preview is admitted only when the host can say which Agents exist for the
Goal the plan names, and nothing in production supplied that. A model-authored
preview was therefore always dropped, so the intake shipped in #4519/#4522/#4524
could not surface at all. The Turn owner now supplies the facts, per Goal.

The manager channel is not bound to one Goal, so admission receives a lookup
rather than one Goal's facts: the owner's own channel resolves any registered
Goal, an external manager channel resolves only the Goals its scope resolver
authorizes, and a Goal the registry does not know - or one outside that channel's
scope - resolves to "cannot describe this Goal", which drops the preview instead
of validating it against another Goal's Agents. The lookup runs once per
proposal, for the Goal the proposal names, and re-resolves the channel scope each
time rather than caching an authorization decision.

The contract now distinguishes two answers that used to look alike: an empty
Agent list is a fact, so the plan's lanes become typed `agent_not_registered`
gaps, while an unresolvable Goal surfaces nothing at all. `parse_agent_response`
carries the context through to the four segments that parse an answer (managed
dsh, Codex agent, ACP, provider HTTP), so the same admission rule applies to
every transport instead of only the one the steward happens to run on.

Verified: tests/test_steward_team_plan_preview.py, tests/test_steward_team_plan_apply.py,
tests/test_chat_manager_context.py and tests/test_attached_session_broker.py 63 passed,
including a preview for a Goal outside the channel scope that is dropped, a
per-Goal lookup that is asked only about the named Goal, a Goal with no
registered Agents that becomes a typed gap, and the owner channel resolving any
registered Goal. examples/docs-governance-smoke.py and
examples/loopx-steward-managed-chat-smoke.py ok.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Co-authored-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>

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

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

审查对象:4519@564b88e410c65a2caf3d86a396bea6310bb85c2d(已合并,合并后审计)。merge base 9d9328e2c6fc464d90882fbbec4dc218b2cb14cf;本 PR 自身改动为 loopx/control_plane/work_items/governed_transition_proposal.py +138 与 tests/test_steward_team_plan_preview.py +121,diff 中的两个 RFC 文件(+51/+36)来自已合并的 #4518,已在 47a51277107f59ede2e4412cc42c2103983287ce 单独出具结论。

动机

steward 团队入端需要一个"想生效也生效不了"的预览。RFC 已记录两片交付顺序(先预览契约、后 materializer),这是第一片:没有这一片,下一切片要么自己发明载荷规则,要么接受未校验的计划。

改动思路

把新 kind 放进既有的受治理提案模块,并让"不生效"由三道独立机制保证:载荷里 applies: False、kind 不在 _SETTLEMENT_PHASE_BY_PROPOSAL_KIND 里、回执校验只接受两个 monitor kind。校验器只做规范化与拒绝:Agent 集合与 action kind 作为入参传入(不重述别人的词表),未注册 Agent 自动变成类型化 gap 并保留 declined_first_todo,文本统一走既有的 validate_public_safe_value。

具体改动

一个纯函数(约 105 行)+ 四个常量 + 一个文本/公共安全 helper,以及 9 个测试。

我做的独立核对(13 组真实探针,全部打在真函数上):

  • fail-closed:priority P9、非 advancement_task、未知 action kind、空文本、601 字符文本、重复 lane_id、9 条 lane、凭证样式文本(sk-live-...)、绝对本地路径(/Users/...)——全部以具名 ValueError 拒绝。
  • 有意的接受路径:未注册 Agent → staffing: gap + declined_first_todo(正是"不悄悄填平也不悄悄丢弃");输入里塞 applies: true → 输出仍为 False。
  • "无法落地"的两条独立证据:settle_governed_transition_proposals 对未登记的 kind 直接抛 governed transition proposal kind is unsupported(第 279-282 行);validate_governed_transition_receipts 只接受 continuous_monitor_upsert/complete(第 94-99 行)。也就是说即使载荷标志被绕过,也无法形成合法结算。
  • 惰性:全仓 grep 确认无生产调用方(只有测试调用),与 PR 的 "Honest limitation" 一致;pytest tests/test_steward_team_plan_preview.py → 9 passed;loopx check --scan-path 两文件 → clean;git diff --check 干净。

对主干的风险

主干运行时行为零变化(无调用方、kind 不可结算),因此风险是契约在下一切片落地前的漂移,且已经能观察到三处(均 P3、非阻塞):

  1. 优先级词表被本地重述且已与他人不一致:模块内 STEWARD_TEAM_PLAN_PRIORITIES = ("P0","P1","P2","P3"),但 Todo 层接受 P0–P4(todos/text.py 的 ^\[(P[0-4])\]、todo_semantics.py 的 P([0-4])),goal-start 契约只列 P0–P2。实测:P4 会被拒(first_todo priority is invalid)。既然 Agent 集合与 action kind 都做成了入参,优先级按同样口径处理(或引用 Todo owner 的常量)就能去掉唯一被重述的规则。
  2. 声明式 gap 与注册表不对称:未注册 Agent 会被自动转成 gap,但反过来"Agent 已注册却声明 agent_not_registered"会被照单接受(实测:registered_agent_ids=['a1'] + 该 lane 声明未注册 → 输出 staffing: gap,无任何提示)。owner 会看到一条本可编排的 lane 被"报缺"。加一行校验(已注册时拒绝该 reason,或降级处理)即可;另两个 gap reason 机器无法判定,保持声明式是对的。
  3. 首 Todo 的四条拒绝规则没有测试:参数化 fail-closed 测试覆盖了 kind/schema/lane 数/stop_condition/quota envelope,但没有覆盖 priority 成员、task_class、action kind 成员与文本长度——我手工验证它们确实都 fail closed,但对一个"以拒绝为目的"的校验器来说,未覆盖的分支正是下一切片重构时最容易回归的地方。

我的整体评价

这一片把"预览不能变成效果"做成了结构性事实而不是注释:三道互相独立的机制、入参化的词表、类型化 gap 保留未编排的工作,加上公共安全递归校验。我用 13 组探针独立验证了它拒绝什么、接受什么、以及为什么无法结算。三点保留意见都指向"下一切片之前把契约收紧",不影响本片作为惰性、可回滚的第一步成立。作为已合并精确 head 的合并后审计,证据支持通过。

English verdict: APPROVE (exact head 564b88e)

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