fix(mcp): make recovered author remediation publishable after a dead session #768

Closed
opened 2026-07-20 02:08:47 -05:00 by jcwalker3 · 1 comment
Owner

Summary

After a dead author session, a preserved and fully valid local remediation commit cannot be published through any sanctioned path. Recovery is gated on the local head being equivalent to the head recorded in the issue lock, but producing a clean worktree — the precondition for recovery itself — requires committing, which advances the head past that recorded value. The two requirements are mutually exclusive, so correct author work becomes permanently unpublishable.

Observed live on issue #607 / PR #767 at master d12adabeb1cf7810b1286ca8871a9b680646021c.

Observed state

Fact Value
Issue #607
PR #767, open, base master, mergeable true
Branch feat/issue-607-sentry-incident-bridge
Remote PR head and recorded-lock head cb6ae0ca50ea3fea3beae9c9544c257bf6c9949b
Preserved clean local remediation commit a487f9fdc94388252b9a234976ca0c9c9ea7f706
Author worktree branches/issue-607-sentry-incident-bridge, clean
Formal review review 479, REQUEST_CHANGES by sysadmin at cb6ae0ca, undismissed, not stale
Owner jcwalker3 / prgs-author, unchanged across the dead session

a487f9fd is a strict descendant of cb6ae0ca — verified by git merge-base --is-ancestor cb6ae0ca a487f9fd (exit 0), with cb6ae0ca as its immediate parent. No history was rewritten; the recorded recovery head remains reachable and unmodified.

a487f9fd is the remediation the reviewer's own NEXT_PROMPT on review 479 requested: the AC4 recurrence-comment path for issue #607.

The deadlock

The four gates form a closed cycle. Each is individually correct; the combination admits no exit.

  1. Dirty worktree blocks lock recovery. Dead-session recovery in gitea_lock_issue requires a clean registered worktree. While the remediation exists only as uncommitted changes, recovery is refused — correctly, because dirty state is unverifiable.
  2. Committing cleans the worktree but advances HEAD. The only sanctioned way to reach a clean worktree without discarding the work is to commit it. That moves the local head from cb6ae0ca to a487f9fd.
  3. Re-lock then demands impossible base-equivalence. With the worktree now clean, recovery re-runs and compares the local head against the recorded head. a487f9fd != cb6ae0ca, so the base-equivalence condition fails and recovery is refused a second time — for the opposite reason. Satisfying gate 1 guarantees failing gate 3, and vice versa. There is no ordering of sanctioned operations that satisfies both.
  4. Downstream gates never receive the recovery evidence. Even if recovery were permitted, the commit, push, create-PR, and duplicate-assessment gates are not passed the server-derived recovered-ownership evidence. recovered_owning_pr does not reach them, so an owning PR that the recovery path already proved belongs to this exact author reappears at the duplicate gate as a competing PR and blocks publication independently.

Net effect: a REQUEST_CHANGES verdict cannot be remediated after a session death. The author holds proven ownership, a clean worktree, an unrewritten descendant commit, and an open PR that is theirs — and still has no sanctioned route to publish.

Why the tempting workarounds are wrong

Each apparent escape launders false evidence through a guard and must stay blocked:

  • resetting the local branch back to cb6ae0ca — discards valid remediation work;
  • hand-editing the issue-lock JSON to advance the recorded head — fabricates ownership evidence;
  • restarting the daemon to clear the dead session — masks the condition without proving ownership;
  • force-pushing or rewriting history to make the heads match — destroys the reviewed base;
  • opening a second PR for the same issue — creates exactly the duplicate the gates exist to prevent.

The fix must make the legitimate path reachable, not weaken the gates that block these.

Root cause

Base-equivalence is enforced as strict head equality against the head recorded at lock time. Equality is the right invariant for detecting rewritten or foreign history, but it is too strong for the only clean-worktree state a recovering author can actually reach. A commit that is a strict descendant of the recorded head, on the same branch, in the same registered worktree, by the same claimant, against the same issue and PR, preserves every property equality was protecting: the recorded head is still reachable, still an ancestor, and still unmodified.

Separately, recovery evidence is computed server-side and then discarded at the boundary. The commit, push, create-PR, and duplicate-assessment gates re-derive ownership from scratch with no knowledge that recovery already proved it, so they re-block work the server has already sanctioned.

Acceptance criteria

AC1. Permit fail-closed recovery when the clean local head is a strict descendant of the recorded PR head, and repository, issue, PR, author, branch, worktree, and prior-lock ownership all match. Every one of these is required; any missing or contradictory element fails closed.

AC2. Propagate server-derived recovery evidence through the commit, push, create-PR, and duplicate-assessment gates, so an owning PR already proven to belong to this author is not re-blocked downstream as a competing PR.

AC3. Continue blocking foreign, mismatched, rewritten, dirty, fabricated, and competing recovery attempts. Specifically: a local head that is not a descendant of the recorded head; a rewritten or force-moved recorded head; a dirty worktree; a different branch, worktree, claimant, profile, issue, or PR; a competing live lock or competing owning PR; and caller-supplied assertions of recovery eligibility.

AC4. Add regression tests reproducing the #607/#767 REQUEST_CHANGES remediation sequence end to end: dead session, dirty worktree refused, commit produces a clean descendant head, recovery permitted, and publication succeeding through commit, push, and PR update.

AC5. Preserve all existing identity, WIP, duplicate-work, containment, and reviewer-separation protections. Author, reviewer, and merger separation is unchanged; no recovery path may grant review, approval, or merge capability.

AC6. Recovery evidence must be server-derived. Do not introduce a caller-controlled boolean or any other parameter by which a session can declare its own recovery eligibility, descendant status, or ownership.

AC7. Successful recovery atomically records provenance: prior session identity, replacement session identity, recorded head, accepted descendant head, the ancestry proof, and the claimant.

AC8. Do not special-case issue #607, PR #767, commit a487f9fd, or any repository-specific identifier. The evidence above is reproduction material, not a fixture.

Validation expectations

  • Focused unit tests for the descendant-recovery disposition, covering each rejection reason independently.
  • An MCP-level regression through native gitea_lock_issue and the downstream publication tools, not only against the assessor functions.
  • An explicit regression proving a non-descendant local head is still refused, so relaxing equality cannot silently become "accept any clean head".
  • Full-suite results reported against the known pre-existing master drift baseline (#737) rather than as an absolute pass count.

Linkage and ownership boundary

  • #760 — open; exact-owner renewal of expired locks under a live daemon PID. Adjacent but a different trigger: that is lease expiry with an unchanged head, this is session death with an advanced head. Do not re-own or merge scope.
  • #753 — closed; dead-PID dead-session recovery under an unexpired lease. Introduced the recovery path this issue extends.
  • #755 — closed; owning-PR duplicate gate during dead-session recovery. AC2 extends evidence propagation into that gate.
  • #607, PR #767, review 479, local commit a487f9fd, and both #607 worktrees (branches/issue-607-sentry-incident-bridge and branches/baseline-master-issue-607) — evidence only. Do not modify, remediate, review, merge, reset, or delete any of them as part of this work.

Verify current line numbers and call sites during implementation rather than treating any reported location as immutable.

Canonical issue state

STATE: ready-for-author
WHO_IS_NEXT: author
NEXT_ACTION: Lock this issue in a separately bounded author task, then implement AC1-AC8
NEXT_PROMPT: Author the strict-descendant dead-session recovery disposition and downstream recovery-evidence propagation; PR; stop
## Summary After a dead author session, a preserved and fully valid local remediation commit cannot be published through any sanctioned path. Recovery is gated on the local head being equivalent to the head recorded in the issue lock, but producing a clean worktree — the precondition for recovery itself — requires committing, which advances the head past that recorded value. The two requirements are mutually exclusive, so correct author work becomes permanently unpublishable. Observed live on issue #607 / PR #767 at master `d12adabeb1cf7810b1286ca8871a9b680646021c`. ## Observed state | Fact | Value | |---|---| | Issue | #607 | | PR | #767, open, base `master`, mergeable true | | Branch | `feat/issue-607-sentry-incident-bridge` | | Remote PR head **and** recorded-lock head | `cb6ae0ca50ea3fea3beae9c9544c257bf6c9949b` | | Preserved clean local remediation commit | `a487f9fdc94388252b9a234976ca0c9c9ea7f706` | | Author worktree | `branches/issue-607-sentry-incident-bridge`, clean | | Formal review | review 479, `REQUEST_CHANGES` by `sysadmin` at `cb6ae0ca`, undismissed, not stale | | Owner | `jcwalker3` / `prgs-author`, unchanged across the dead session | `a487f9fd` is a strict descendant of `cb6ae0ca` — verified by `git merge-base --is-ancestor cb6ae0ca a487f9fd` (exit 0), with `cb6ae0ca` as its immediate parent. No history was rewritten; the recorded recovery head remains reachable and unmodified. `a487f9fd` is the remediation the reviewer's own `NEXT_PROMPT` on review 479 requested: the AC4 recurrence-comment path for issue #607. ## The deadlock The four gates form a closed cycle. Each is individually correct; the combination admits no exit. 1. **Dirty worktree blocks lock recovery.** Dead-session recovery in `gitea_lock_issue` requires a clean registered worktree. While the remediation exists only as uncommitted changes, recovery is refused — correctly, because dirty state is unverifiable. 2. **Committing cleans the worktree but advances HEAD.** The only sanctioned way to reach a clean worktree without discarding the work is to commit it. That moves the local head from `cb6ae0ca` to `a487f9fd`. 3. **Re-lock then demands impossible base-equivalence.** With the worktree now clean, recovery re-runs and compares the local head against the recorded head. `a487f9fd != cb6ae0ca`, so the base-equivalence condition fails and recovery is refused a second time — for the opposite reason. Satisfying gate 1 guarantees failing gate 3, and vice versa. There is no ordering of sanctioned operations that satisfies both. 4. **Downstream gates never receive the recovery evidence.** Even if recovery were permitted, the commit, push, create-PR, and duplicate-assessment gates are not passed the server-derived recovered-ownership evidence. `recovered_owning_pr` does not reach them, so an owning PR that the recovery path already proved belongs to this exact author reappears at the duplicate gate as a competing PR and blocks publication independently. Net effect: a `REQUEST_CHANGES` verdict cannot be remediated after a session death. The author holds proven ownership, a clean worktree, an unrewritten descendant commit, and an open PR that is theirs — and still has no sanctioned route to publish. ## Why the tempting workarounds are wrong Each apparent escape launders false evidence through a guard and must stay blocked: * resetting the local branch back to `cb6ae0ca` — discards valid remediation work; * hand-editing the issue-lock JSON to advance the recorded head — fabricates ownership evidence; * restarting the daemon to clear the dead session — masks the condition without proving ownership; * force-pushing or rewriting history to make the heads match — destroys the reviewed base; * opening a second PR for the same issue — creates exactly the duplicate the gates exist to prevent. The fix must make the legitimate path reachable, not weaken the gates that block these. ## Root cause Base-equivalence is enforced as strict head equality against the head recorded at lock time. Equality is the right invariant for detecting rewritten or foreign history, but it is too strong for the only clean-worktree state a recovering author can actually reach. A commit that is a strict descendant of the recorded head, on the same branch, in the same registered worktree, by the same claimant, against the same issue and PR, preserves every property equality was protecting: the recorded head is still reachable, still an ancestor, and still unmodified. Separately, recovery evidence is computed server-side and then discarded at the boundary. The commit, push, create-PR, and duplicate-assessment gates re-derive ownership from scratch with no knowledge that recovery already proved it, so they re-block work the server has already sanctioned. ## Acceptance criteria **AC1.** Permit fail-closed recovery when the clean local head is a strict descendant of the recorded PR head, and repository, issue, PR, author, branch, worktree, and prior-lock ownership all match. Every one of these is required; any missing or contradictory element fails closed. **AC2.** Propagate server-derived recovery evidence through the commit, push, create-PR, and duplicate-assessment gates, so an owning PR already proven to belong to this author is not re-blocked downstream as a competing PR. **AC3.** Continue blocking foreign, mismatched, rewritten, dirty, fabricated, and competing recovery attempts. Specifically: a local head that is not a descendant of the recorded head; a rewritten or force-moved recorded head; a dirty worktree; a different branch, worktree, claimant, profile, issue, or PR; a competing live lock or competing owning PR; and caller-supplied assertions of recovery eligibility. **AC4.** Add regression tests reproducing the #607/#767 `REQUEST_CHANGES` remediation sequence end to end: dead session, dirty worktree refused, commit produces a clean descendant head, recovery permitted, and publication succeeding through commit, push, and PR update. **AC5.** Preserve all existing identity, WIP, duplicate-work, containment, and reviewer-separation protections. Author, reviewer, and merger separation is unchanged; no recovery path may grant review, approval, or merge capability. **AC6.** Recovery evidence must be server-derived. Do not introduce a caller-controlled boolean or any other parameter by which a session can declare its own recovery eligibility, descendant status, or ownership. **AC7.** Successful recovery atomically records provenance: prior session identity, replacement session identity, recorded head, accepted descendant head, the ancestry proof, and the claimant. **AC8.** Do not special-case issue #607, PR #767, commit `a487f9fd`, or any repository-specific identifier. The evidence above is reproduction material, not a fixture. ## Validation expectations * Focused unit tests for the descendant-recovery disposition, covering each rejection reason independently. * An MCP-level regression through native `gitea_lock_issue` and the downstream publication tools, not only against the assessor functions. * An explicit regression proving a non-descendant local head is still refused, so relaxing equality cannot silently become "accept any clean head". * Full-suite results reported against the known pre-existing master drift baseline (#737) rather than as an absolute pass count. ## Linkage and ownership boundary * **#760** — open; exact-owner renewal of expired locks under a live daemon PID. Adjacent but a different trigger: that is lease expiry with an unchanged head, this is session death with an advanced head. Do not re-own or merge scope. * **#753** — closed; dead-PID dead-session recovery under an unexpired lease. Introduced the recovery path this issue extends. * **#755** — closed; owning-PR duplicate gate during dead-session recovery. AC2 extends evidence propagation into that gate. * **#607**, **PR #767**, **review 479**, local commit `a487f9fd`, and both #607 worktrees (`branches/issue-607-sentry-incident-bridge` and `branches/baseline-master-issue-607`) — **evidence only**. Do not modify, remediate, review, merge, reset, or delete any of them as part of this work. Verify current line numbers and call sites during implementation rather than treating any reported location as immutable. ## Canonical issue state ```text STATE: ready-for-author WHO_IS_NEXT: author NEXT_ACTION: Lock this issue in a separately bounded author task, then implement AC1-AC8 NEXT_PROMPT: Author the strict-descendant dead-session recovery disposition and downstream recovery-evidence propagation; PR; stop ```
jcwalker3 added status:pr-open and removed status:ready labels 2026-07-20 02:53:04 -05:00
Author
Owner

Canonical Issue State

STATE:
pr_open_awaiting_independent_review

WHO_IS_NEXT:
reviewer

NEXT_ACTION:
Independent prgs-reviewer acquires a reviewer PR lease on PR #769, validates at pinned head 5547399037, and posts a native review verdict.

NEXT_PROMPT:

Role: REVIEWER
Repo: prgs / Scaled-Tech-Consulting / Gitea-Tools
Issue: #768
PR: #769
Pinned head: 554739903795bca34e0bbbdf56542d3e74deb606

Review strict-descendant dead-session recovery against AC1–AC8. Confirm
exact-head recovery still works, clean strict-descendant recovery is
sanctioned only with server-side ancestry proof, dirty/diverged/behind/
live-owner/malformed-ancestry cases fail closed, and recovery evidence
reaches commit/create-PR/duplicate gates. Do not merge. Do not touch
#607 / PR #767 / a487f9fd (evidence only).

WHAT_HAPPENED:
Author implemented AC1–AC8 on branch fix/issue-768-descendant-recovery, published commit 5547399037 via gitea_commit_files, opened PR #769 against master. Issue labels transitioned to status:pr-open.

WHY:
Exact head equality made post-dead-session remediation unpublishable; strict-descendant recovery with server-side ancestry proof is the minimal fix that preserves fail-closed ownership gates.

RELATED_PRS:
#769

BLOCKERS:
none

VALIDATION:
Focused recovery suites: 90 passed (+2 subtests). Broader lock/workspace suites: 171 passed. Full suite: 3737 passed, 6 skipped, 2 failed (pre-existing #737 drift only). Head 5547399037. Preserved evidence untouched: #607, PR #767, review #479, a487f9fd.

LAST_UPDATED_BY:
jcwalker3 / prgs-author

## Canonical Issue State STATE: pr_open_awaiting_independent_review WHO_IS_NEXT: reviewer NEXT_ACTION: Independent prgs-reviewer acquires a reviewer PR lease on PR #769, validates at pinned head 554739903795bca34e0bbbdf56542d3e74deb606, and posts a native review verdict. NEXT_PROMPT: ```text Role: REVIEWER Repo: prgs / Scaled-Tech-Consulting / Gitea-Tools Issue: #768 PR: #769 Pinned head: 554739903795bca34e0bbbdf56542d3e74deb606 Review strict-descendant dead-session recovery against AC1–AC8. Confirm exact-head recovery still works, clean strict-descendant recovery is sanctioned only with server-side ancestry proof, dirty/diverged/behind/ live-owner/malformed-ancestry cases fail closed, and recovery evidence reaches commit/create-PR/duplicate gates. Do not merge. Do not touch #607 / PR #767 / a487f9fd (evidence only). ``` WHAT_HAPPENED: Author implemented AC1–AC8 on branch fix/issue-768-descendant-recovery, published commit 554739903795bca34e0bbbdf56542d3e74deb606 via gitea_commit_files, opened PR #769 against master. Issue labels transitioned to status:pr-open. WHY: Exact head equality made post-dead-session remediation unpublishable; strict-descendant recovery with server-side ancestry proof is the minimal fix that preserves fail-closed ownership gates. RELATED_PRS: #769 BLOCKERS: none VALIDATION: Focused recovery suites: 90 passed (+2 subtests). Broader lock/workspace suites: 171 passed. Full suite: 3737 passed, 6 skipped, 2 failed (pre-existing #737 drift only). Head 554739903795bca34e0bbbdf56542d3e74deb606. Preserved evidence untouched: #607, PR #767, review #479, a487f9fd. LAST_UPDATED_BY: jcwalker3 / prgs-author
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Scaled-Tech-Consulting/Gitea-Tools#768