Fixes the six defects from the formal REQUEST_CHANGES review at
889931d553:
- F1: run sanctioned stale-binding recovery in
gitea_resolve_task_capability BEFORE the terminal launcher probe, so a
worktree removed mid-session can no longer wedge mutation resolution on
'missing cwd' before recovery runs. The fail-closed probe block now also
carries the binding classification evidence.
- F2: _resolve_preflight_workspace_path resolves with the same
verify_paths existence checks as the canonical mutation context, and
_get_workspace_porcelain returns a synthetic tracked-dirty sentinel when
the workspace is missing or git fails — an uninspectable workspace can
never read as clean/empty porcelain.
- F3: the orphaned_expired_superseded_head cleanup matrix now requires
affirmative safe worktree evidence (worktree_clean=true or
worktree_exists=false), aligned with the sibling orphaned_owner_missing
gate and the diagnosis path; unknown evidence fails closed.
- F4: reviewer-session-lease shadows and stale-binding audit records are
recovery-critical kinds exempt from the 4h session-state TTL; they
persist until a sanctioned clear terminally reconciles them.
- F5: session-lease shadows are keyed by lease session id (collision-safe
identity) with a list_states enumeration API; concurrent same-profile
sessions can no longer overwrite or misattribute each other's crash
evidence. Legacy single-slot records remain readable.
- F6: the durable audit record is persisted (status=pending) BEFORE the
environment binding is cleared; if audit persistence fails the clear
does not happen and the failure is reported explicitly.
30 new regression tests cover deleted-worktree recovery ordering, missing/
deleted/symlinked/mid-evaluation path changes, unknown-evidence cleanup,
TTL boundary (before/at/after), interleaved and reconnected concurrent
sessions, and audit-write failure/retry/idempotence/ordering.
Issue #704 dotenv load-path prevention is intentionally NOT included.
Full suite: 2695 passed, 6 skipped, 161 subtests passed.
Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
+58
-5
@@ -413,30 +413,48 @@ def record_session_lease(
|
||||
|
||||
def clear_session_lease() -> None:
|
||||
global _SESSION_LEASE
|
||||
prior = _SESSION_LEASE
|
||||
_SESSION_LEASE = None
|
||||
_persist_session_lease_shadow(None)
|
||||
_persist_session_lease_shadow(None, prior=prior)
|
||||
|
||||
|
||||
def get_session_lease() -> dict[str, Any] | None:
|
||||
return dict(_SESSION_LEASE) if _SESSION_LEASE else None
|
||||
|
||||
|
||||
def _persist_session_lease_shadow(lease: dict[str, Any] | None) -> None:
|
||||
def _persist_session_lease_shadow(
|
||||
lease: dict[str, Any] | None,
|
||||
*,
|
||||
prior: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""Best-effort durable shadow of the in-session lease (#702).
|
||||
|
||||
A daemon that dies without teardown leaves this record behind, giving a
|
||||
later process provable orphan evidence (owner pid + session id) for the
|
||||
guarded cleanup path. Observability only — mutation gates never read it,
|
||||
and failures here must never block lease operations.
|
||||
|
||||
Shadows are keyed by lease session id (#702 F5): concurrent same-profile
|
||||
sessions each own a distinct record, so one session's heartbeat can never
|
||||
overwrite or misattribute another's crash evidence. A sanctioned clear is
|
||||
the terminal reconciliation of that record's lifecycle.
|
||||
"""
|
||||
try:
|
||||
import mcp_session_state as mss
|
||||
|
||||
if lease is None:
|
||||
sid = ((prior or {}).get("session_id") or "").strip() or None
|
||||
if sid:
|
||||
mss.clear_state(
|
||||
kind=mss.KIND_REVIEWER_SESSION_LEASE, instance_id=sid
|
||||
)
|
||||
# Legacy single-slot record from pre-F5 code: clearing it is safe
|
||||
# because collision-safe writes never target that key again.
|
||||
mss.clear_state(kind=mss.KIND_REVIEWER_SESSION_LEASE)
|
||||
return
|
||||
mss.save_state(
|
||||
kind=mss.KIND_REVIEWER_SESSION_LEASE,
|
||||
instance_id=((lease.get("session_id") or "").strip() or None),
|
||||
payload={
|
||||
"pr_number": lease.get("pr_number"),
|
||||
"session_id": lease.get("session_id"),
|
||||
@@ -454,12 +472,38 @@ def _persist_session_lease_shadow(lease: dict[str, Any] | None) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def load_session_lease_shadow() -> dict[str, Any] | None:
|
||||
"""Load the durable session-lease shadow left by this profile identity."""
|
||||
def load_session_lease_shadow(
|
||||
session_id: str | None = None,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Load the durable session-lease shadow left by this profile identity.
|
||||
|
||||
With *session_id*, load that session's collision-safe record (#702 F5),
|
||||
falling back to the legacy single-slot record only when its payload names
|
||||
the same session. Without *session_id*, return the legacy record or the
|
||||
sole surviving instance record — ambiguity (multiple candidates) returns
|
||||
``None`` because a shadow that cannot be attributed proves nothing.
|
||||
"""
|
||||
try:
|
||||
import mcp_session_state as mss
|
||||
|
||||
return mss.load_state(kind=mss.KIND_REVIEWER_SESSION_LEASE)
|
||||
sid = (session_id or "").strip()
|
||||
if sid:
|
||||
record = mss.load_state(
|
||||
kind=mss.KIND_REVIEWER_SESSION_LEASE, instance_id=sid
|
||||
)
|
||||
if record is not None:
|
||||
return record
|
||||
legacy = mss.load_state(kind=mss.KIND_REVIEWER_SESSION_LEASE)
|
||||
if legacy and (legacy.get("session_id") or "").strip() == sid:
|
||||
return legacy
|
||||
return None
|
||||
legacy = mss.load_state(kind=mss.KIND_REVIEWER_SESSION_LEASE)
|
||||
if legacy is not None:
|
||||
return legacy
|
||||
records = mss.list_states(kind=mss.KIND_REVIEWER_SESSION_LEASE)
|
||||
if len(records) == 1:
|
||||
return records[0]
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@@ -1006,6 +1050,15 @@ def assess_obsolete_reviewer_comment_lease_cleanup(
|
||||
fail_closed_reasons.append(
|
||||
"owner process absent but worktree dirty; cleanup denied"
|
||||
)
|
||||
elif not (worktree_clean is True or worktree_exists is False):
|
||||
# #702 F3: unknown worktree evidence must fail closed, exactly
|
||||
# like the sibling orphaned_owner_missing gate and the
|
||||
# diagnosis path — cleanup needs affirmative safe evidence.
|
||||
fail_closed_reasons.append(
|
||||
"owner process absent but worktree evidence is unknown; "
|
||||
"cleanup requires affirmative safe evidence "
|
||||
"(worktree_clean=true or worktree_exists=false)"
|
||||
)
|
||||
elif head_superseded and not has_terminal:
|
||||
classification = "ambiguous_conflicting_evidence"
|
||||
fail_closed_reasons.append(
|
||||
|
||||
Reference in New Issue
Block a user