fix: authoritative cross-role generic queue allocation (Closes #840)
Add controller-owned cross_role allocation mode that inspects the full queue and returns one selection with required role/profile/action and lease evidence. Document process_work_queue routing, normalize controller role metadata, and keep the dashboard explanatory only. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
+41
-6
@@ -234,12 +234,25 @@ def _effective_workspace_role() -> str:
|
||||
|
||||
|
||||
def _profile_role_kind(profile: dict) -> str:
|
||||
"""Resolve a profile's declared role before inferring from permissions."""
|
||||
role = (profile.get("role") or profile.get("role_kind") or "").strip()
|
||||
"""Resolve a profile's declared role before inferring from permissions.
|
||||
|
||||
Declared ``role`` / ``role_kind`` always wins so a controller profile is
|
||||
never reclassified as reconciler from permission inference (#840).
|
||||
"""
|
||||
role = (profile.get("role") or profile.get("role_kind") or "").strip().lower()
|
||||
if role:
|
||||
# Normalize aliases / case.
|
||||
if "control" in role:
|
||||
return "controller"
|
||||
return role
|
||||
profile_name = (profile.get("profile_name") or "").strip().lower()
|
||||
for candidate in ("reconciler", "merger", "reviewer", "author"):
|
||||
for candidate in (
|
||||
"controller",
|
||||
"reconciler",
|
||||
"merger",
|
||||
"reviewer",
|
||||
"author",
|
||||
):
|
||||
if candidate in profile_name:
|
||||
return candidate
|
||||
return _role_kind(
|
||||
@@ -15538,7 +15551,8 @@ def mcp_get_control_plane_guide(
|
||||
profile = get_profile()
|
||||
allowed = profile["allowed_operations"]
|
||||
forbidden = profile["forbidden_operations"]
|
||||
role = _role_kind(allowed, forbidden)
|
||||
# Prefer declared profile role so controller is not mislabeled reconciler (#840).
|
||||
role = _profile_role_kind(profile)
|
||||
username = _authenticated_username(h)
|
||||
|
||||
identity = {
|
||||
@@ -15597,6 +15611,16 @@ def mcp_get_control_plane_guide(
|
||||
"user, and merging requires explicit operator authorization plus the "
|
||||
"'MERGE PR <n>' confirmation. "
|
||||
"Review and merge are separate workflow roles. A reviewer approval is not merge authorization.")
|
||||
elif role == "controller":
|
||||
guidance.append(
|
||||
"Controller profile: route work via "
|
||||
"gitea_route_task_session(task_type='process_work_queue') then "
|
||||
"gitea_allocate_next_work (allocation_mode=cross_role by default). "
|
||||
"The allocator returns exactly one authoritative selection with "
|
||||
"required_role / required_profile / selected_action. Do not "
|
||||
"implement, review, approve, or merge in this session — schedule "
|
||||
"the matching role namespace instead. Dashboard output is "
|
||||
"explanatory only and never replaces allocator selection.")
|
||||
elif role == "mixed":
|
||||
guidance.append(
|
||||
"WARNING: this profile allows both authoring and "
|
||||
@@ -15806,7 +15830,8 @@ def gitea_whoami(
|
||||
"environment": profile.get("environment"),
|
||||
"service": profile.get("service"),
|
||||
"identity": profile.get("identity"),
|
||||
"role": profile.get("role"),
|
||||
"role": profile.get("role") or _profile_role_kind(profile),
|
||||
"role_kind": _profile_role_kind(profile),
|
||||
"profile_address": profile.get("profile_path"),
|
||||
"execution_profile": profile.get("execution_profile"),
|
||||
"audit_label": profile.get("audit_label"),
|
||||
@@ -20590,9 +20615,10 @@ def gitea_allocate_next_work(
|
||||
candidates_json: Any = None,
|
||||
exclude_issue_numbers: list[int] | None = None,
|
||||
expected_candidate_set_fingerprint: str | None = None,
|
||||
allocation_mode: str | None = None,
|
||||
limit: int = 50,
|
||||
) -> dict:
|
||||
"""Controller-owned next-work allocator using the #613 control-plane DB (#600).
|
||||
"""Controller-owned next-work allocator using the #613 control-plane DB (#600/#840).
|
||||
|
||||
Workers must not self-select exclusive work under the standard multi-LLM
|
||||
workflow. Call this tool instead.
|
||||
@@ -20602,6 +20628,14 @@ def gitea_allocate_next_work(
|
||||
``ControlPlaneDB.assign_and_lease`` (never file locks or comment-only
|
||||
leases as the coordination source).
|
||||
|
||||
*allocation_mode* (#840): when the active role is controller (or mode is
|
||||
``cross_role``), inspect the complete queue and return exactly one
|
||||
authoritative selection with selected item, action, required_role,
|
||||
required_profile/namespace, pins, and allocation/lease evidence.
|
||||
Role-scoped workers pass ``role=author|reviewer|merger|reconciler`` (or
|
||||
omit for profile role) for single-role filtering. Controller routes only
|
||||
and does not perform downstream mutations.
|
||||
|
||||
Outcomes include: ``assigned_work``, ``preview``, ``wait``,
|
||||
``blocked_by_terminal_path``, ``no_safe_work``, ``role_ineligible``,
|
||||
``blocked_by_excluded_own_lease``, ``candidate_set_drift``.
|
||||
@@ -20736,6 +20770,7 @@ def gitea_allocate_next_work(
|
||||
controller_instance_id=allocator_service.resolve_controller_instance_id(),
|
||||
exclude_issue_numbers=exclude_issue_numbers,
|
||||
expected_candidate_set_fingerprint=expected_candidate_set_fingerprint,
|
||||
allocation_mode=allocation_mode,
|
||||
)
|
||||
except ValueError as exc:
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user