Merge branch 'master' into fix/issue-897-permission-stale-runtime-classification
This commit is contained in:
+57
-3
@@ -22569,12 +22569,17 @@ def gitea_request_mcp_restart(
|
||||
request_override: bool = False,
|
||||
session_id: str | None = None,
|
||||
limit: int = 200,
|
||||
restart_class: str = "full_mcp_restart",
|
||||
target_session_id: str | None = None,
|
||||
target_role: str | None = None,
|
||||
target_connector: str | None = None,
|
||||
drain_proof_json: str | None = None,
|
||||
request_break_glass: bool = False,
|
||||
) -> dict:
|
||||
"""Evaluate a proposed MCP restart and return an impact preview (#658).
|
||||
|
||||
Central restart coordinator: gathers live control-plane state (sessions,
|
||||
Central restart coordinator: resolves the requested restart class, gathers
|
||||
live control-plane state (sessions,
|
||||
leases/locks, in-flight issue/PR work, mutations, worktrees) and returns a
|
||||
blast-radius impact report with a ``safe`` / ``unsafe`` / ``override``
|
||||
verdict, so the console (#642/#652) and operators can see what a restart
|
||||
@@ -22588,7 +22593,16 @@ def gitea_request_mcp_restart(
|
||||
only when ``request_break_glass`` is set *and* the environment carries
|
||||
``GITEA_BREAKGLASS_RESTART_AUTHORIZATION``. Even an authorized gate performs
|
||||
no restart here; actual execution is a further child. The gate outcome is
|
||||
reported under ``apply_gate`` / ``apply_authorized``.
|
||||
reported under ``apply_gate``.
|
||||
|
||||
``apply_authorized`` requires **both** authorizations to pass: the #661 drain
|
||||
gate *and* the #663 restart-class matrix (``allow_restart``). They are
|
||||
independent — the drain gate proves the blast radius was drained and knows
|
||||
nothing about whether this requester may request this class — so a class the
|
||||
matrix denied never reports an authorized apply. Break-glass bypasses the
|
||||
drain proof only; it never bypasses the class matrix. ``apply_gate`` carries
|
||||
``drain_gate_allow`` and ``restart_class_authorized`` so a denial is
|
||||
attributable to the authorization that produced it.
|
||||
|
||||
Operator override authority is read from the process environment
|
||||
(``GITEA_OPERATOR_RESTART_OVERRIDE_AUTHORIZATION``), never self-asserted by
|
||||
@@ -22673,6 +22687,9 @@ def gitea_request_mcp_restart(
|
||||
|
||||
profile = get_profile()
|
||||
profile_name = (profile.get("profile_name") or "").strip() or "session"
|
||||
requester_role = (
|
||||
profile.get("role_kind") or profile.get("role") or ""
|
||||
).strip().lower()
|
||||
sid = (session_id or "").strip() or f"{profile_name}-{os.getpid()}"
|
||||
|
||||
# Override authority is read from the environment only — a worker session
|
||||
@@ -22682,6 +22699,15 @@ def gitea_request_mcp_restart(
|
||||
(os.environ.get("GITEA_OPERATOR_RESTART_OVERRIDE_AUTHORIZATION") or "").strip()
|
||||
)
|
||||
operator_override = bool(request_override and operator_authorized)
|
||||
controller_approved = bool(
|
||||
(
|
||||
os.environ.get("GITEA_CONTROLLER_RESTART_APPROVAL_AUTHORIZATION")
|
||||
or ""
|
||||
).strip()
|
||||
)
|
||||
requester_permissions = restart_coordinator.permissions_for_role(
|
||||
requester_role
|
||||
)
|
||||
|
||||
inventory = {
|
||||
"sessions": sessions,
|
||||
@@ -22696,6 +22722,14 @@ def gitea_request_mcp_restart(
|
||||
operator_override=operator_override,
|
||||
requesting_session_id=sid,
|
||||
dry_run=True, # coordinator is always analysis-only (#658)
|
||||
restart_class=restart_class,
|
||||
requester_role=requester_role,
|
||||
requester_permissions=requester_permissions,
|
||||
controller_approved=controller_approved,
|
||||
operator_authorized=operator_authorized,
|
||||
target_session_id=target_session_id,
|
||||
target_role=target_role,
|
||||
target_connector=target_connector,
|
||||
)
|
||||
|
||||
payload = report.as_dict()
|
||||
@@ -22707,6 +22741,9 @@ def gitea_request_mcp_restart(
|
||||
payload["requesting_session_id"] = sid
|
||||
payload["operator_override_requested"] = bool(request_override)
|
||||
payload["operator_override_authorized"] = operator_authorized
|
||||
payload["controller_approval_authorized"] = controller_approved
|
||||
payload["requester_role"] = requester_role
|
||||
payload["requester_permissions"] = list(requester_permissions)
|
||||
# Actual restart execution remains a further child; this tool never restarts
|
||||
# a process. What #661 adds is the *hard gate*: an apply request (dry_run
|
||||
# False) must present a valid, unexpired, clean drain proof, or it is denied
|
||||
@@ -22744,8 +22781,25 @@ def gitea_request_mcp_restart(
|
||||
gate_payload["reasons"] = [proof_parse_error] + list(
|
||||
gate_payload.get("reasons") or []
|
||||
)
|
||||
# The #663 restart-class matrix and the #661 drain gate are two
|
||||
# independent authorizations, and an apply requires BOTH. ``gate.allow``
|
||||
# proves only that the blast radius was drained — or that break-glass
|
||||
# was authorized — and knows nothing about whether this requester may
|
||||
# request this class at all. Conjoining them keeps a class the matrix
|
||||
# denied from ever reporting an authorized apply, and keeps break-glass
|
||||
# scoped to what it is for: bypassing the drain proof, never the
|
||||
# least-privilege class matrix.
|
||||
restart_class_authorized = bool(report.allow_restart)
|
||||
gate_payload["drain_gate_allow"] = bool(gate.allow)
|
||||
gate_payload["restart_class_authorized"] = restart_class_authorized
|
||||
if not restart_class_authorized:
|
||||
gate_payload["reasons"] = list(gate_payload.get("reasons") or []) + [
|
||||
"restart class authorization denied; apply denied regardless of "
|
||||
"drain proof or break-glass (fail closed, #663)",
|
||||
*(report.authorization_reasons or []),
|
||||
]
|
||||
payload["apply_gate"] = gate_payload
|
||||
payload["apply_authorized"] = gate.allow
|
||||
payload["apply_authorized"] = bool(gate.allow and restart_class_authorized)
|
||||
payload["break_glass_requested"] = bool(request_break_glass)
|
||||
payload["break_glass_authorized"] = break_glass_authorized
|
||||
# Even an authorized gate performs no restart here: execution is a later
|
||||
|
||||
Reference in New Issue
Block a user