From 04cf5c4e1cab9c0301d7afcfb4864d3d152695d5 Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Wed, 16 Sep 2026 19:16:23 +0800 Subject: [PATCH] feat(steward): record the intent basis a materialized team plan advances A team plan creates work-graph edits, and nothing tied those edits to the canonical revision they were meant to advance. The settlement now reads that basis before it writes and publishes it in the receipt. The basis is the Goal's own `shared_goal_alignment_v0` source basis, read for one of the plan's Agents (the source basis is a Goal-level fact, so any lane's Agent reads the same one, and a ready lane is preferred because that is where the work now lives). A Goal whose basis cannot be read omits the field rather than inventing one, and the value is validated as a bounded digest shape. The read happens before the lanes are created on purpose: computing it afterwards would let the edit describe itself instead of describing the revision it advanced. The receipt is the traceability record, so the Todo row itself needs no new field. The Chat action's own receipt carries the same basis, so the owner's readback can name what the new lanes advance. Verified: tests/test_steward_team_plan_apply.py, tests/test_chat_team_plan_action.py, tests/extensions/test_governed_capability_execution.py and tests/test_chat_operation_actions.py 35 passed, including a receipt basis that equals a fresh pre-apply projection read, differs from a post-apply read, still validates, and rejects a malformed digest. examples/docs-governance-smoke.py ok. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- .../rfcs/harness-selection-dsh-pi-v0.md | 17 +++-- .../rfcs/harness-selection-dsh-pi-v0.zh-CN.md | 11 ++-- loopx/chat_actions.py | 5 ++ .../governed_transition_proposal.py | 64 ++++++++++++++++++- tests/test_chat_team_plan_action.py | 3 + tests/test_steward_team_plan_apply.py | 41 ++++++++++++ 6 files changed, 128 insertions(+), 13 deletions(-) diff --git a/docs/architecture/rfcs/harness-selection-dsh-pi-v0.md b/docs/architecture/rfcs/harness-selection-dsh-pi-v0.md index 2bfd6ae851..9e020c9ddd 100644 --- a/docs/architecture/rfcs/harness-selection-dsh-pi-v0.md +++ b/docs/architecture/rfcs/harness-selection-dsh-pi-v0.md @@ -555,13 +555,16 @@ Shipped enforcement, in delivery order: staffing has drifted. What is still missing is the surface that sends that confirmation and the -traceability behind it: a multi-lane preview has no frontend confirmation -surface yet, and a materialized lane Todo does not carry the canonical intent -revision it is meant to advance. The readback is no longer one of those gaps: the -apply publishes every lane Todo it ensured under a bounded `lane_todo_ids` field, -that field is the one additive exception to the closed, persisted receipt field -set so a receipt written before it still validates, and a team-plan receipt -carries no monitor key because a plan is not a monitor. +per-Goal coverage behind it: a multi-lane preview has no frontend confirmation +surface yet. Traceability is recorded rather than implied: the settlement reads +the Goal's canonical source basis before it writes, and the receipt carries it as +a bounded `intent_basis`, so each lane Todo can be tied to the revision it was +meant to advance even though the Todo row itself does not carry the field. The +readback is no longer a gap either: the apply publishes every lane Todo it ensured +under a bounded `lane_todo_ids` field, both fields are additive exceptions to the +closed, persisted receipt field set so a receipt written before them still +validates, and a team-plan receipt carries no monitor key because a plan is not a +monitor. ### Relationship to the multi-agent and shared-authority contracts diff --git a/docs/architecture/rfcs/harness-selection-dsh-pi-v0.zh-CN.md b/docs/architecture/rfcs/harness-selection-dsh-pi-v0.zh-CN.md index a4a899c2a3..1977537a3b 100644 --- a/docs/architecture/rfcs/harness-selection-dsh-pi-v0.zh-CN.md +++ b/docs/architecture/rfcs/harness-selection-dsh-pi-v0.zh-CN.md @@ -430,11 +430,12 @@ Todo 创建、quota 或 goal policy——复用预览点名的身份,不得扩 建出首个有界 Todo 并返回 lane 回读。预览与落地之间若发生注册变化,提案会变为 stale,而 不是把 staffing 已经漂移的计划落地。 -仍然缺的是**发出这次确认的表面**与它背后的可追溯性:多 lane 预览还没有前端确认面;建出的 -lane Todo 也还没有携带它本应推进的规范意图修订。回读本身已经不再是缺口:落地会把这次确保的每 -一条 lane Todo 以有界字段 `lane_todo_ids` 发布出去;该字段是那个封闭且持久化的回执字段集的 -**唯一**加性例外,因此早前写下的回执仍然通过校验,而团队计划回执不带 monitor key——计划不是 -monitor。 +仍然缺的是**发出这次确认的表面**:多 lane 预览还没有前端确认面。可追溯性已经被记录而不是被 +暗示:结算在写入**之前**读取该 Goal 的规范 source basis,回执以有界字段 `intent_basis` +携带它,因此每条 lane Todo 都能被追溯回它本应推进的那个修订——尽管 Todo 行本身还不携带该 +字段。回读也不再是缺口:落地会把这次确保的每一条 lane Todo 以有界字段 `lane_todo_ids` 发布 +出去;这两个字段都是那个封闭且持久化的回执字段集的加性例外,因此早前写下的回执仍然通过校验, +而团队计划回执不带 monitor key——计划不是 monitor。 ### 与 multi-agent / shared authority 契约的关系 diff --git a/loopx/chat_actions.py b/loopx/chat_actions.py index 6294b80573..5b2c702f01 100644 --- a/loopx/chat_actions.py +++ b/loopx/chat_actions.py @@ -967,6 +967,7 @@ def _apply_team_plan( ) settlement = settlements[0] lane_todo_ids = [str(item) for item in (settlement.get("lane_todo_ids") or [])] + intent_basis = str(settlement.get("intent_basis") or "") receipt = { "receipt_id": _digest( { @@ -987,6 +988,10 @@ def _apply_team_plan( "lane_todo_ids": lane_todo_ids, }, } + if intent_basis: + # The canonical revision these lanes were created against, so the + # owner's readback can name what the work advances. + receipt["intent_basis"] = intent_basis stored = self.store.apply( proposal_id, current_state_fingerprint=current_fingerprint, receipt=receipt ) diff --git a/loopx/control_plane/work_items/governed_transition_proposal.py b/loopx/control_plane/work_items/governed_transition_proposal.py index ad67f03133..54d192c261 100644 --- a/loopx/control_plane/work_items/governed_transition_proposal.py +++ b/loopx/control_plane/work_items/governed_transition_proposal.py @@ -44,9 +44,10 @@ # closed and a new field is admitted only as an explicitly bounded addition # that an older receipt may still omit. `lane_todo_ids` is the readback of a # team plan: every lane Todo the settlement ensured, not just the first one. -_OPTIONAL_RECEIPT_FIELDS = {"lane_todo_ids"} +_OPTIONAL_RECEIPT_FIELDS = {"lane_todo_ids", "intent_basis"} _LANE_TODO_ID_LIMIT = 8 _LANE_TODO_ID = re.compile(r"^todo_[A-Za-z0-9]{1,40}$") +_INTENT_BASIS = re.compile(r"^sha256:[0-9a-f]{64}$") TransitionCheckpoint = Callable[[list[dict[str, Any]]], None] @@ -152,6 +153,14 @@ def validate_governed_transition_receipts( raise ValueError( "governed transition proposal receipt lane_todo_ids is invalid" ) + intent_basis = receipt.get("intent_basis") + if intent_basis is not None and ( + not isinstance(intent_basis, str) + or not _INTENT_BASIS.fullmatch(intent_basis) + ): + raise ValueError( + "governed transition proposal receipt intent_basis is invalid" + ) validate_public_safe_value(receipt, path=f"transition_receipts[{index}]") receipts.append(receipt) return receipts @@ -254,6 +263,48 @@ def _upsert_monitor( } +def _intent_basis_for( + *, + goal_id: str, + goal: Mapping[str, Any], + registry_path: Path, + preview: Mapping[str, Any], +) -> str | None: + """Read the canonical source basis one work-graph edit is applied against. + + The source basis is a Goal-level fact, so any of the Goal's Agents reads the + same one; a ready lane is preferred because that is where the work will live. + A Goal whose basis cannot be read omits the field rather than inventing one. + """ + + lanes = preview.get("lanes") or [] + basis_agent = next( + ( + str(lane.get("agent_id")) + for lane in lanes + if lane.get("staffing") == "ready" + ), + str(lanes[0].get("agent_id")) if lanes else "", + ) + if not basis_agent: + return None + try: + from ...control_plane.goals.shared_goal_alignment import ( + project_shared_goal_alignment, + ) + + alignment = project_shared_goal_alignment( + goal_id=goal_id, + agent_id=basis_agent, + project=Path(str(goal.get("repo") or ".")).expanduser(), + registry_path=Path(registry_path), + ) + except (OSError, ValueError, TypeError, KeyError, RuntimeError): + return None + basis = (alignment.get("source_basis") or {}).get("source_basis_digest") + return str(basis) if basis else None + + def _apply_team_plan( *, registry_path: Path, @@ -299,6 +350,11 @@ def _apply_team_plan( registered_agent_ids=registered_agent_ids_for_goal(goal), supported_action_kinds=sorted(TODO_ACTION_KIND_ADVANCEMENT_VALUES), ) + # Traceability is read before the edit: the receipt names the canonical + # basis this work-graph edit was applied against, so the lanes could not + # make the basis describe their own creation. A Goal whose basis cannot be + # read omits the field instead of inventing one. + intent_basis = _intent_basis_for(goal_id=goal_id, goal=goal, registry_path=registry_path, preview=preview) created: list[str] = [] reused: list[str] = [] for lane in preview["lanes"]: @@ -330,6 +386,7 @@ def _apply_team_plan( "target_key": None, "created_todo_ids": created, "lane_todo_ids": lane_todo_ids, + "intent_basis": intent_basis, "reused_lane_count": len(reused), "gap_count": len(preview["gaps"]), } @@ -458,6 +515,11 @@ def settle_governed_transition_proposals( # The apply ensured every ready lane's first Todo; a receipt that # named only the first one could not be read as "what exists now". receipt["lane_todo_ids"] = [str(item) for item in lane_todo_ids] + if result.get("intent_basis"): + # The work-graph edit this receipt records is traceable to the + # canonical basis it was applied against, so a lane Todo can be tied + # back to the intent revision it was meant to advance. + receipt["intent_basis"] = str(result["intent_basis"]) validate_public_safe_value(receipt, path="transition_receipt") receipts.append(receipt) by_proposal_id[proposal_id] = receipt diff --git a/tests/test_chat_team_plan_action.py b/tests/test_chat_team_plan_action.py index cd03a05ffb..b2427e7ec2 100644 --- a/tests/test_chat_team_plan_action.py +++ b/tests/test_chat_team_plan_action.py @@ -122,6 +122,9 @@ def test_a_confirmed_plan_creates_each_ready_lane_first_todo(tmp_path: Path) -> lane_todo_ids = receipt["resource_ids"]["lane_todo_ids"] assert len(lane_todo_ids) == 1 and lane_todo_ids[0].startswith("todo_") assert receipt["resource_ids"]["todo_id"] == lane_todo_ids[0] + # The readback names the canonical revision these lanes were created + # against, so the owner sees what the new work is meant to advance. + assert receipt["intent_basis"].startswith("sha256:") state = _todos(project) assert "Advance the intake contract" in state assert f"claimed_by={AGENT_ID}" in state diff --git a/tests/test_steward_team_plan_apply.py b/tests/test_steward_team_plan_apply.py index 4cab5f9e0d..12b3df84ef 100644 --- a/tests/test_steward_team_plan_apply.py +++ b/tests/test_steward_team_plan_apply.py @@ -3,6 +3,7 @@ from __future__ import annotations import json +import re from pathlib import Path import pytest @@ -288,6 +289,46 @@ def test_the_lane_readback_is_optional_bounded_and_additive() -> None: validate_governed_transition_receipts([_receipt(monitor_key=None)]) +def test_the_receipt_records_the_intent_basis_it_was_applied_against( + tmp_path: Path, +) -> None: + """A work-graph edit is traceable to the canonical basis it advanced.""" + + from loopx.control_plane.goals.shared_goal_alignment import ( + project_shared_goal_alignment, + ) + + project, registry_path = _fixture(tmp_path) + + def basis() -> str: + return project_shared_goal_alignment( + goal_id=GOAL_ID, + agent_id=AGENT_ID, + project=project, + registry_path=registry_path, + )["source_basis"]["source_basis_digest"] + + before = basis() + receipts = _settle(registry_path, _proposal()) + recorded = receipts[0]["intent_basis"] + assert re.fullmatch(r"sha256:[0-9a-f]{64}", recorded) + # The receipt names the revision the edit was applied against, not the one + # the edit itself produced, and it is the canonical basis rather than a + # digest this module invented. + assert recorded == before + assert basis() != before + assert len(validate_governed_transition_receipts(receipts)) == 1 + + for malformed in ("sha256:short", "2836abc7", "sha256:" + "A" * 64): + with pytest.raises(ValueError, match="intent_basis is invalid"): + validate_governed_transition_receipts([_receipt(intent_basis=malformed)]) + assert len( + validate_governed_transition_receipts( + [_receipt(intent_basis="sha256:" + "a" * 64)] + ) + ) == 1 + + def test_a_team_plan_receipt_must_not_invent_a_monitor_key() -> None: """A plan is not a monitor, so its receipt carries no monitor identity."""