Skip to content

Commit 35ef3ba

Browse files
committed
fix(control-plane): bind wait exemptions to exact current targets
Signed-off-by: huangruiteng <huangrt01@163.com>
1 parent 1046ea4 commit 35ef3ba

6 files changed

Lines changed: 131 additions & 18 deletions

File tree

‎examples/shared-goal-authority-e2e/mutants.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ def command(self) -> list[str]:
4747

4848

4949
CASES = [
50+
Case('delivery_wait_target_unbound', (('loopx/control_plane/todos/resume_condition.ts', replacement(
51+
'condition.target_todo_id !== spec.target || ', '')),),
52+
'tests/control_plane_ts/delivery_response.test.ts', 'exact dependency identity'),
53+
Case('delivery_wait_unknown_class', (('loopx/control_plane/todos/resume_condition.ts', replacement(
54+
'["advancement_task", "user_gate", "user_action", "blocker"].includes(String(condition.target_task_class))',
55+
'true')),),
56+
'tests/control_plane_ts/delivery_response.test.ts', 'exact dependency identity'),
5057
Case('rollout_cwd_root', (('loopx/cli_rollout.py', replacement(
5158
'resolve_runtime_root(registry, runtime_root_arg, registry_path=registry_path)',
5259
'resolve_runtime_root(registry, runtime_root_arg)')),),

‎loopx/control_plane/todos/resume_condition.ts‎

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -432,22 +432,35 @@ function diagnosedCondition(condition: JsonObject, waitingTodoId: string): JsonO
432432

433433
/** Positive wait proof for consumers that may relax supervision. Historical
434434
* absence of an invalid marker is not proof of a valid, identified target. */
435-
export function resumeConditionHasKnownPendingTarget(condition: JsonObject): boolean {
435+
export function resumeConditionHasKnownPendingTarget(condition: JsonObject, waitingTodo: JsonObject): boolean {
436+
const spec = parseResumeWhen(waitingTodo.resume_when);
437+
if (!spec || condition.resume_when !== spec.normalized || condition.kind !== spec.kind
438+
|| (condition.target !== undefined && condition.target !== spec.target)) return false;
436439
if (condition.schema_version !== "todo_resume_condition_v0" || condition.satisfied !== false
437-
|| resumeAvailabilityReason(condition) !== "resume_condition_pending") return false;
438-
switch (condition.kind) {
440+
|| diagnoseTodoResumeCondition(condition, String(waitingTodo.todo_id)).state !== "pending") return false;
441+
if ((spec.kind === "todo_done" || spec.kind === "monitor_changed")
442+
&& (condition.target_todo_id !== spec.target || spec.target === waitingTodo.todo_id)) return false;
443+
switch (spec.kind) {
439444
case "todo_done":
440445
return ["open", "deferred"].includes(String(condition.target_status))
441-
&& typeof condition.target_task_class === "string" && condition.target_task_class !== "continuous_monitor"
446+
&& ["advancement_task", "user_gate", "user_action", "blocker"].includes(String(condition.target_task_class))
442447
&& (condition.target_archive_state === null || condition.target_archive_state === "active");
443448
case "monitor_changed":
444449
return condition.target_task_class === "continuous_monitor" && condition.target_status === "open"
445-
&& typeof condition.baseline_generation === "number" && condition.baseline_generation >= 0
450+
&& typeof condition.baseline_generation === "number" && Number.isSafeInteger(condition.baseline_generation)
451+
&& condition.baseline_generation >= 0 && condition.baseline_generation === waitingTodo.resume_monitor_generation
446452
&& typeof condition.material_change_generation === "number"
453+
&& Number.isSafeInteger(condition.material_change_generation) && condition.material_change_generation >= 0
447454
&& condition.material_change_generation <= condition.baseline_generation;
448-
case "capacity_available": return condition.provider_required === false;
449-
case "pr_merged": return typeof condition.pr_repo === "string" && condition.pr_repo.length > 0
450-
&& condition.repository_binding_state !== "ambiguous";
455+
case "capacity_available": return condition.provider_required === false
456+
&& condition.provider === "runtime_available_capabilities" && condition.capability === spec.target;
457+
case "pr_merged": {
458+
const ref = normalizedPrRef(spec.target);
459+
const repository = ref?.repo ?? githubRepository(waitingTodo.task_repository);
460+
return ref !== null && repository !== null && condition.pr_repo === repository
461+
&& condition.pr_number === ref.number && condition.repository_binding_state !== "ambiguous"
462+
&& condition.repository_binding_source === (ref.repo ? "qualified_resume_when" : "task_repository");
463+
}
451464
default: return false;
452465
}
453466
}

‎loopx/control_plane/work_items/delivery_history.py‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,16 @@ def project_delivery_response(
9393
source = next((item for item in todo_planning_source_items(summary, include_terminal=True)
9494
if item.get("todo_id") == run.get("todo_id")), None) if summary else None
9595
fields = ("todo_id", "role", "status", "task_class", "archive_state", "claimed_by",
96-
"excluded_agents", "resume_when", "resume_ready", "resume_condition")
96+
"excluded_agents", "resume_when", "resume_ready", "resume_condition",
97+
"resume_monitor_generation", "task_repository")
9798
todo = {key: source[key] for key in fields if key in source} if source else None
9899
if todo and isinstance(todo.get("resume_condition"), dict):
99100
condition = todo["resume_condition"]
100101
todo["resume_condition"] = {key: condition[key] for key in (
101102
"schema_version", "resume_when", "satisfied", "invalid_target", "invalid_state",
102-
"kind", "target_todo_id", "target_status", "target_task_class", "target_archive_state",
103-
"baseline_generation", "material_change_generation", "provider_required", "pr_repo", "repository_binding_state",
103+
"kind", "target", "target_todo_id", "target_status", "target_task_class", "target_archive_state",
104+
"baseline_generation", "material_change_generation", "provider_required", "provider", "capability",
105+
"pr_repo", "pr_number", "repository_binding_state", "repository_binding_source",
104106
) if key in condition}
105107
result = effect_runtime_result("work_item.delivery_response.project", {
106108
"run": _run_facts(run), "todo": todo,

‎loopx/control_plane/work_items/delivery_history.ts‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export function projectDeliveryResponse(value: unknown): JsonObject {
118118
runs: [input.run], outcome_floor_configured: true }).runs as DeliverySignal[])[0];
119119
const todo = input.todo === null ? null : requireJsonObject(input.todo, "bound Todo");
120120
const runAgent = typeof input.run_agent_id === "string" ? input.run_agent_id.trim() : "";
121-
const agentId = typeof input.agent_id === "string" ? input.agent_id.trim() : runAgent;
121+
const agentId = typeof input.agent_id === "string" ? input.agent_id.trim() : "";
122122
const owner = typeof todo?.claimed_by === "string" ? todo.claimed_by.trim() : "";
123123
const excluded = Array.isArray(todo?.excluded_agents) ? todo.excluded_agents : [];
124124
const condition = todo?.resume_condition && typeof todo.resume_condition === "object"
@@ -131,12 +131,10 @@ export function projectDeliveryResponse(value: unknown): JsonObject {
131131
&& todo.role === "agent" && todo.task_class === "advancement_task"
132132
&& ["open", "deferred"].includes(String(todo.status))
133133
&& (todo.archive_state === undefined || todo.archive_state === "active")
134-
&& agentId === runAgent && (!owner || owner === agentId) && !excluded.includes(agentId)
134+
&& Boolean(agentId) && agentId === runAgent && (!owner || owner === agentId) && !excluded.includes(agentId)
135135
&& condition?.schema_version === "todo_resume_condition_v0"
136-
&& condition.resume_when === todo.resume_when && Boolean(todo.resume_when)
137-
&& condition.target_todo_id !== todo.todo_id
138136
&& condition.satisfied === false && todo.resume_ready !== true
139-
&& resumeConditionHasKnownPendingTarget(condition);
137+
&& resumeConditionHasKnownPendingTarget(condition, todo);
140138
return {
141139
schema_version: "delivery_response_v0",
142140
outcome_floor_applicable: !canonicalWait,

‎tests/control_plane/test_delivery_response.py‎

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
"""Current canonical waits constrain historical supervision, not vice versa."""
22
from copy import deepcopy
3+
import pytest
34

45
from loopx.control_plane.handoff.delivery_contract import handoff_delivery_contract
56
from loopx.control_plane.testing.quota_fixtures import quota_status_payload, quota_todo_item, quota_todo_summary
67
from loopx.control_plane.work_items.delivery_history import project_delivery_response
8+
from loopx.control_plane.todos.summary_item import TODO_PLANNING_SOURCE_KEYS
9+
from loopx.control_plane.todos.resume_condition import evaluate_todo_resume_conditions
710
from loopx.control_plane.work_items.work_lane_context import build_work_lane_context_contract
811
from loopx.quota import build_quota_should_run, quota_with_handoff_outcome_floor
912
from loopx.status import project_post_handoff_history
@@ -23,14 +26,41 @@ def waiting_todo():
2326
return quota_todo_item(todo_id="todo_delivery", title="Wait for the dependency", status="deferred",
2427
claimed_by="agent-a", resume_when="todo_done:todo_dependency", resume_ready=False,
2528
resume_condition={"schema_version": "todo_resume_condition_v0", "resume_when": "todo_done:todo_dependency",
26-
"satisfied": False, "kind": "todo_done", "target_status": "open",
29+
"satisfied": False, "kind": "todo_done", "target_todo_id": "todo_dependency", "target_status": "open",
2730
"target_task_class": "advancement_task", "target_archive_state": "active"})
2831

2932

3033
def dependency_todo():
3134
return quota_todo_item(todo_id="todo_dependency", title="Complete prerequisite", claimed_by="agent-b")
3235

3336

37+
@pytest.mark.parametrize("patch", [
38+
{"target_todo_id": "todo_other"}, {"target_todo_id": None},
39+
{"target_task_class": "unknown_class"}, {"target_task_class": ""},
40+
{"kind": "monitor_changed"},
41+
])
42+
def test_stale_or_malformed_target_cannot_suppress_any_consumer(patch):
43+
todo = waiting_todo()
44+
extra = quota_todo_item(todo_id="todo_alternative", title="Continue independent work", claimed_by="agent-a")
45+
summary = quota_todo_summary([todo, dependency_todo(), extra], claim_scope_agent_id="agent-a")
46+
# Mutate the evaluated snapshot, not the authoring input: summary building
47+
# legitimately recomputes supplied conditions from its canonical source.
48+
for key in TODO_PLANNING_SOURCE_KEYS:
49+
for row in summary.get(key, []):
50+
if row["todo_id"] == "todo_delivery":
51+
row["resume_condition"].update(patch)
52+
readiness = project_post_handoff_history([blocked_run()] * 3, PROFILE)
53+
asset = {"execution_profile": PROFILE, "agent_todos": summary}
54+
item = {"handoff_readiness": readiness, "agent_todos": summary, "project_asset": asset}
55+
before = deepcopy(item)
56+
assert project_delivery_response(blocked_run(), summary)["reason"] == "history_supervision"
57+
assert quota_with_handoff_outcome_floor({"state": "eligible"}, waiting_on="codex",
58+
project_asset=asset, handoff_readiness=readiness)["state"] == "focus_wait"
59+
assert handoff_delivery_contract(item) is not None
60+
assert build_work_lane_context_contract(item, agent_todo_summary=summary)["must_attempt_work"] is True
61+
assert item == before
62+
63+
3464
def test_status_compaction_preserves_binding_and_all_consumers_defer_to_current_wait():
3565
readiness = project_post_handoff_history([blocked_run()] * 3, PROFILE)
3666
summary = quota_todo_summary([waiting_todo(), dependency_todo()], claim_scope_agent_id="agent-a")
@@ -55,6 +85,26 @@ def test_status_compaction_preserves_binding_and_all_consumers_defer_to_current_
5585
assert "outcome_followthrough" not in lane
5686

5787

88+
@pytest.mark.parametrize("resume, patch", [
89+
("monitor_changed:todo_dependency", {"baseline_generation": 1}),
90+
("capacity_available:network", {"capability": "other"}),
91+
("pr_merged:#1", {"pr_number": 2}),
92+
])
93+
def test_real_resume_projection_identity_survives_python_transport(resume, patch):
94+
todo = {**waiting_todo(), "resume_when": resume, "resume_monitor_generation": 0,
95+
"task_repository": "git:github.com/example/project"}
96+
dependency = {**dependency_todo(), "task_class": "continuous_monitor", "material_change_generation": 0}
97+
condition = evaluate_todo_resume_conditions([todo], source_items=[dependency], available_capabilities=[])[todo["todo_id"]]
98+
summary = quota_todo_summary([todo, dependency], claim_scope_agent_id="agent-a")
99+
for key in TODO_PLANNING_SOURCE_KEYS:
100+
for row in summary.get(key, []):
101+
if row["todo_id"] == todo["todo_id"]:
102+
row["resume_condition"] = condition
103+
assert project_delivery_response(blocked_run(), summary)["reason"] == "canonical_todo_wait"
104+
condition.update(patch)
105+
assert project_delivery_response(blocked_run(), summary)["reason"] == "history_supervision"
106+
107+
58108
def test_surface_supervision_remains_and_invalid_wait_does_not_clear_floor():
59109
summary = quota_todo_summary([waiting_todo()], claim_scope_agent_id="agent-a")
60110
asset = {"execution_profile": PROFILE, "agent_todos": summary}

‎tests/control_plane_ts/delivery_response.test.ts‎

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const waiting = { todo_id: "todo_delivery", role: "agent", status: "deferred",
1313
task_class: "advancement_task", claimed_by: "agent-a", resume_when: "todo_done:todo_dependency",
1414
resume_ready: false, resume_condition: { schema_version: "todo_resume_condition_v0",
1515
resume_when: "todo_done:todo_dependency", satisfied: false, kind: "todo_done",
16-
target_status: "open", target_task_class: "advancement_task", target_archive_state: "active" } };
16+
target_todo_id: "todo_dependency", target_status: "open", target_task_class: "advancement_task", target_archive_state: "active" } };
1717
const input = { run, todo: waiting, run_agent_id: "agent-a", agent_id: "agent-a" };
1818

1919
test("wait proof consumes the real resume evaluator for all four condition kinds", () => {
@@ -34,9 +34,37 @@ test("wait proof consumes the real resume evaluator for all four condition kinds
3434
const condition = (evaluated.conditions as JsonObject[])[0].condition;
3535
assert.equal(projectDeliveryResponse({ ...input, todo: { ...todo, resume_condition: condition } }).outcome_floor_applicable,
3636
!valid, resume + JSON.stringify(source));
37+
if (valid) {
38+
const proof = condition as JsonObject;
39+
const mutations: JsonObject[] = [{ kind: "unknown" }, { target: "other_target" }];
40+
if (proof.kind === "todo_done" || proof.kind === "monitor_changed") {
41+
mutations.push({ target_todo_id: "todo_other" }, { target_todo_id: null }, { target_task_class: "unknown" });
42+
}
43+
if (proof.kind === "monitor_changed") mutations.push(
44+
{ baseline_generation: 1 }, { material_change_generation: -1 }, { material_change_generation: 0.5 });
45+
if (proof.kind === "capacity_available") mutations.push({ capability: "other" }, { provider_required: true });
46+
if (proof.kind === "pr_merged") mutations.push({ pr_number: 2 }, { pr_repo: "example/other" });
47+
for (const patch of mutations) assert.equal(projectDeliveryResponse({ ...input,
48+
todo: { ...todo, resume_condition: { ...proof, ...patch } } }).reason,
49+
"history_supervision", resume + JSON.stringify(patch));
50+
}
3751
}
3852
});
3953

54+
test("known non-monitor completion classes and repository-bound PR waits remain supported", () => {
55+
for (const task_class of ["advancement_task", "user_gate", "user_action", "blocker"]) {
56+
assert.equal(projectDeliveryResponse({ ...input, todo: { ...waiting,
57+
resume_condition: { ...waiting.resume_condition, target_task_class: task_class } } }).reason, "canonical_todo_wait");
58+
}
59+
const todo = { ...waiting, resume_when: "pr_merged:#1", task_repository: "git:github.com/example/project" };
60+
const evaluated = evaluateTodoResumeConditions({ schema_version: TODO_RESUME_EVALUATION_REQUEST_SCHEMA_VERSION,
61+
items: [todo], source_items: [], rollout_events: [], available_capabilities: [] });
62+
const condition = (evaluated.conditions as JsonObject[])[0].condition;
63+
assert.equal(projectDeliveryResponse({ ...input, todo: { ...todo, resume_condition: condition } }).reason, "canonical_todo_wait");
64+
assert.equal(projectDeliveryResponse({ ...input, todo: { ...todo, task_repository: "git:github.com/example/other",
65+
resume_condition: condition } }).reason, "history_supervision");
66+
});
67+
4068
test("a bound blocked observation delegates a current legal wait to canonical planning", () => {
4169
const before = structuredClone(input);
4270
const result = projectDeliveryResponse(input);
@@ -46,9 +74,24 @@ test("a bound blocked observation delegates a current legal wait to canonical pl
4674
assert.deepEqual(input, before);
4775
});
4876

77+
test("exact dependency identity and a supported completion class are required", () => {
78+
for (const patch of [
79+
{ target_todo_id: "todo_other" }, { target_todo_id: null },
80+
{ target: "todo_other" }, { kind: "capacity_available" },
81+
{ target_task_class: "unknown_class" }, { target_task_class: "" },
82+
]) {
83+
const result = projectDeliveryResponse({ ...input, todo: { ...waiting,
84+
resume_condition: { ...waiting.resume_condition, ...patch } } });
85+
assert.equal(result.reason, "history_supervision", JSON.stringify(patch));
86+
assert.equal(result.outcome_floor_applicable, true);
87+
assert.equal((result.outcome_followthrough as JsonObject).required, true);
88+
}
89+
});
90+
4991
test("history alone, missing source, invalid wait, and other actors cannot exempt the floor", () => {
5092
for (const patch of [
5193
{ todo: null }, { agent_id: "agent-b" }, { run_agent_id: "agent-b" },
94+
{ agent_id: null }, { run_agent_id: null }, { agent_id: "", run_agent_id: "" },
5295
{ todo: { ...waiting, todo_id: "todo_other" } },
5396
{ todo: { ...waiting, claimed_by: "agent-b" } },
5497
{ todo: { ...waiting, excluded_agents: ["agent-a"] } },

0 commit comments

Comments
 (0)