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:
2026-07-23 00:05:35 -04:00
co-authored by Claude Opus 4.8
parent 689c60fc7c
commit 648d9464ba
7 changed files with 992 additions and 56 deletions
+19 -5
View File
@@ -24,7 +24,12 @@ ROLE_WORKTREE_ENVS: dict[str, str] = {
"reconciler": RECONCILER_WORKTREE_ENV,
}
NON_AUTHOR_ROLES = frozenset({"reviewer", "merger", "reconciler"})
# Controller has no task worktree env — it routes only (#840).
KNOWN_ROLE_KINDS = frozenset(
{"author", "reviewer", "merger", "reconciler", "controller"}
)
NON_AUTHOR_ROLES = frozenset({"reviewer", "merger", "reconciler", "controller"})
def normalize_role_kind(
@@ -37,8 +42,12 @@ def normalize_role_kind(
profile = (profile_name or "").strip().lower()
if role == "reviewer" and "merger" in profile:
return "merger"
if "controller" in profile or role == "controller":
return "controller"
if role in ROLE_WORKTREE_ENVS:
return role
if role in KNOWN_ROLE_KINDS:
return role
return "author"
@@ -80,7 +89,7 @@ def resolve_namespace_workspace(
"""
env_map = env if env is not None else os.environ
role = normalize_role_kind(role_kind, profile_name=profile_name)
role_env_key = ROLE_WORKTREE_ENVS[role]
role_env_key = ROLE_WORKTREE_ENVS.get(role)
# #618: durable author resolution — no silent control/master fallback.
if role == "author" and verify_paths:
@@ -108,13 +117,17 @@ def resolve_namespace_workspace(
)
return workspace, source
role_env_candidate = (
(_env_value(env_map, role_env_key), f"{role_env_key} environment variable", True)
if role_env_key
else (None, "no role worktree env", True)
)
for candidate, source, env_sourced in (
(worktree_path, "worktree_path argument", False),
(worktree, "worktree argument", False),
(_env_value(env_map, ACTIVE_WORKTREE_ENV),
f"{ACTIVE_WORKTREE_ENV} environment variable", True),
(_env_value(env_map, role_env_key),
f"{role_env_key} environment variable", True),
role_env_candidate,
(session_lease_worktree if role in {"reviewer", "merger"} else None,
"reviewer PR lease worktree", False),
# Author lock derivation is handled by the durable path above when
@@ -433,7 +446,8 @@ def assess_namespace_mutation_workspace(
reasons.append(
f"{role} mutation blocked: workspace is the stable control checkout; "
f"create or reconnect to a session-owned worktree under branches/ "
f"or set {ROLE_WORKTREE_ENVS[role]} / {ACTIVE_WORKTREE_ENV}"
f"or set {ROLE_WORKTREE_ENVS.get(role, ACTIVE_WORKTREE_ENV)} / "
f"{ACTIVE_WORKTREE_ENV}"
)
elif (
role in {"reviewer", "merger"}