Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions loopx/control_plane/effect_runtime_handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ export function createEffectRuntimeHandlers(
lazyHandler(() => import("./coordination/legacy_writer_fence.ts"), ({checkLegacyCoordinationWriteAllowed}) => checkLegacyCoordinationWriteAllowed),
],
["task_lease.write_scopes.overlap", lazyHandler(() => import("./work_items/task_lease_acquire_decision.ts"), ({evaluateTaskLeaseWriteScopesOverlap}) => evaluateTaskLeaseWriteScopesOverlap)],
["quota.task_orchestration.write_scope_overlap", lazyHandler(() => import("./work_items/task_lease_acquire_decision.ts"), ({evaluateQuotaSchedulingWriteScopesOverlap}) => evaluateQuotaSchedulingWriteScopesOverlap)],
["quota.monitor_poll.commit", lazyHandler(() => import("./quota/monitor_poll_commit.ts"), ({evaluateQuotaMonitorPollCommit}) => evaluateQuotaMonitorPollCommit)],
["presentation.decision_notice.project", lazyHandler(() => import("./presentation/decision_notice.ts"), ({projectDecisionNotice}) => projectDecisionNotice)],
["presentation.decision_notice.validate_references", lazyHandler(() => import("./presentation/decision_notice.ts"), ({validateDecisionNoticeReferences}) => validateDecisionNoticeReferences)],
Expand Down
42 changes: 12 additions & 30 deletions loopx/control_plane/quota/task_orchestration_admission.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from __future__ import annotations

from dataclasses import dataclass
from fnmatch import fnmatchcase
from typing import Any

from ..agents.capability_gate import missing_required_capabilities
from ..agents.runtime_model import peer_work_key, select_peer_for_work
from ..effect_runtime import effect_runtime_result
from ..todos.contract import (
normalize_required_capabilities,
normalize_required_write_scopes,
Expand Down Expand Up @@ -472,37 +472,19 @@ def _write_scopes_conflict(
left: _NormalizedAdmissionLane,
right: _NormalizedAdmissionLane,
) -> bool:
"""Consume the scheduling overlap decision owned by the canonical lease module."""

if not left.required_write_scopes or not right.required_write_scopes:
return False
if left.effective_repository != right.effective_repository:
return False
return any(
_scope_patterns_overlap(left_scope, right_scope)
for left_scope in left.required_write_scopes
for right_scope in right.required_write_scopes
)


def _scope_patterns_overlap(left: str, right: str) -> bool:
if left == right or fnmatchcase(left, right) or fnmatchcase(right, left):
return True
left_prefix = _scope_static_prefix(left)
right_prefix = _scope_static_prefix(right)
return bool(
left_prefix
and right_prefix
and (
left_prefix == right_prefix
or left_prefix.startswith(f"{right_prefix}/")
or right_prefix.startswith(f"{left_prefix}/")
)
decision = effect_runtime_result(
"quota.task_orchestration.write_scope_overlap",
{
"left": list(left.required_write_scopes),
"right": list(right.required_write_scopes),
},
)


def _scope_static_prefix(scope: str) -> str:
parts: list[str] = []
for part in str(scope or "").strip("/").split("/"):
if any(marker in part for marker in ("*", "?", "[")):
break
parts.append(part)
return "/".join(parts)
if not isinstance(decision, dict) or not isinstance(decision.get("overlap"), bool):
raise RuntimeError("quota scheduling write-scope decision shape mismatch")
return bool(decision["overlap"])
36 changes: 36 additions & 0 deletions loopx/control_plane/work_items/task_lease_acquire_decision.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,42 @@ export function evaluateTaskLeaseWriteScopesOverlap(value: unknown): JsonObject
};
}

/** Literal segments before the first wildcard, folded for comparison. */
function schedulingScopeRoot(scope: string): string {
const parts: string[] = [];
for (const part of scope.replace(/^\/+|\/+$/gu, "").split("/")) {
if (part.includes("*") || part.includes("?") || part.includes("[")) break;
parts.push(part);
}
return caseFold(parts.join("/"));
}

function schedulingScopePairOverlaps(left: string, right: string): boolean {
if (scopePairOverlaps(left, right)) return true;
const leftRoot = schedulingScopeRoot(left);
const rightRoot = schedulingScopeRoot(right);
return Boolean(leftRoot && rightRoot && (
leftRoot === rightRoot ||
leftRoot.startsWith(`${rightRoot}/`) ||
rightRoot.startsWith(`${leftRoot}/`)
));
}

/**
* Quota scheduling keeps its own subtree rule: a literal scope owns the paths
* below it even without a trailing slash. That rule is wider than the canonical
* lease rule, so this entry point composes the two instead of restating the
* case fold or the glob matcher.
*/
export function evaluateQuotaSchedulingWriteScopesOverlap(value: unknown): JsonObject {
const input = requireJsonObject(value, "quota scheduling write-scope overlap");
const left = comparisonWriteScopes(decisionStringArray(input.left, "left"));
const right = comparisonWriteScopes(decisionStringArray(input.right, "right"));
const overlap = left.length > 0 && right.length > 0 &&
left.some((first) => right.some((second) => schedulingScopePairOverlaps(first, second)));
return {overlap};
}

function decisionBoolean(value: unknown, label: string): boolean {
if (typeof value !== "boolean") {
throw new EffectRuntimeRequestError(`${label} must be a boolean`);
Expand Down
92 changes: 92 additions & 0 deletions tests/control_plane/test_task_orchestration_admission.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,98 @@ def test_admission_serializes_overlapping_write_scopes() -> None:
]


def _scope_contract(
items: list[dict[str, Any]],
*,
goal_write_scope: list[str],
) -> dict[str, Any] | None:
summary = {"items": items}
contract, _work_lane = apply_task_orchestration_contract(
fallback_work_lane_contract={"lane": "advancement_task"},
goal_boundary={
"write_scope": goal_write_scope,
"orchestration": {
"mode": "multi_subagent",
"spawn_allowed": True,
"max_children": 2,
},
},
agent_identity={
"agent_id": AGENT_ID,
"registered_agents": [AGENT_ID],
},
agent_todo_summary=summary,
raw_agent_todo_summary=summary,
raw_user_todo_summary={"items": []},
available_capabilities=["subagent_spawn"],
)
return contract


def test_admission_serializes_case_alias_write_scopes() -> None:
contract = _scope_contract(
[
_todo("todo_primary", action_kind="implement", required_write_scopes=["src/Main.ts"]),
_todo("todo_case_alias", action_kind="implement", required_write_scopes=["src/main.ts"]),
_todo("todo_independent", action_kind="implement", required_write_scopes=["src/other.ts"]),
],
goal_write_scope=["src/**"],
)

assert contract is not None
assert [lane["todo_id"] for lane in contract["eligible_child_lanes"]] == [
"todo_independent"
]
assert contract["blocked_lanes"] == [
{
"todo_id": "todo_case_alias",
"task_domain": "code",
"reason_codes": ["write_scope_conflict"],
"conflicts_with_todo_id": "todo_primary",
}
]


def test_admission_keeps_scheduling_subtree_containment_without_a_trailing_slash() -> None:
contract = _scope_contract(
[
_todo("todo_primary", action_kind="implement", required_write_scopes=["src"]),
_todo("todo_below", action_kind="implement", required_write_scopes=["src/a.ts"]),
_todo("todo_independent", action_kind="implement", required_write_scopes=["tests/a.py"]),
],
goal_write_scope=["src", "src/**", "tests/**"],
)

assert contract is not None
assert [lane["todo_id"] for lane in contract["eligible_child_lanes"]] == [
"todo_independent"
]
assert contract["blocked_lanes"] == [
{
"todo_id": "todo_below",
"task_domain": "code",
"reason_codes": ["write_scope_conflict"],
"conflicts_with_todo_id": "todo_primary",
}
]


def test_admission_leaves_sibling_files_independent() -> None:
contract = _scope_contract(
[
_todo("todo_primary", action_kind="implement", required_write_scopes=["src/a.ts"]),
_todo("todo_sibling", action_kind="implement", required_write_scopes=["src/b.ts"]),
],
goal_write_scope=["src/**"],
)

assert contract is not None
assert [lane["todo_id"] for lane in contract["eligible_child_lanes"]] == [
"todo_sibling"
]
assert contract["blocked_lanes"] == []


def test_admission_serializes_implicit_and_explicit_goal_repository_scopes() -> None:
summary = {
"items": [
Expand Down
35 changes: 35 additions & 0 deletions tests/control_plane_ts/effect_runtime_handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,41 @@ test("runtime exposes the canonical task-lease write-scope rule", async () => {
assert.equal(result.overlap, true);
});

test("scheduling write-scope overlap folds case and keeps subtree containment", async () => {
const rows: [string, string, boolean][] = [
["src/Main.ts", "src/main.ts", true],
["src/MAIN.ts", "src/main.ts", true],
["src/\u03a3.ts", "src/\u03c2.ts", true],
["src/\u00df.ts", "src/SS.ts", true],
["src/", "SRC/", true],
["src/", "src/a.ts", true],
["src/*.ts", "src/*.TS", true],
["src/*.ts", "src/a.ts", true],
["src/a.ts", "src/b.ts", false],
["src/main.ts", "src/other.ts", false],
["src/a.ts", "src/a.txt", false],
["src", "src/a.ts", true],
["src/main", "src/main/x", true],
["src/a", "src/a/b", true],
["src/a.ts", "src/a.ts/b", true],
];
for (const [left, right, overlap] of rows) {
const scheduling = await dispatchEffectRuntimeMethod(
handlers,
"quota.task_orchestration.write_scope_overlap",
{ left: [left], right: [right] },
) as Record<string, unknown>;
assert.equal(scheduling.overlap, overlap, `scheduling ${left} vs ${right}`);
}
// The scheduling entry point stays wider than the canonical lease rule.
const canonical = await dispatchEffectRuntimeMethod(
handlers,
"task_lease.write_scopes.overlap",
{ left: ["src"], right: ["src/a.ts"] },
) as Record<string, unknown>;
assert.equal(canonical.overlap, false);
});

test("runtime exposes the source-session lifetime decisions", async () => {
const goalRef = {
goal_id: "release",
Expand Down
Loading