Unify task leases with sliding heartbeats and safe terminal dead-owner recovery #790
Closed
opened 2026-07-21 22:33:14 -05:00 by jcwalker3
·
5 comments
No Branch/Tag Specified
master
fix/issue-987-native-mcp-bootstrap
feat/issue-985-project-scoped-launcher-identity
fix/issue-983-cross-repo-base-ref
feat/issue-980-stale-worker-retirement
fix/issue-975-client-identity-heartbeat
fix/issue-973-cross-repo-canonical-roots
fix/issue-970-safely-resolve-missing-worktrees
fix/issue-969-native-mcp-bootstrap
feat/issue-664-break-glass-restart
feat/issue-708-mcp-namespace-attachment
feat/issue-665-restart-audit
fix/issue-700-durable-walls
fix/issue-704-prevent-env-workspace-bindings
feat/issue-707-cross-project-boundaries
fix/issue-690-review-profile-switch-guard
fix/issue-953-bootstrap-lock-provenance
feat/issue-949-native-fleet-inventory
fix/issue-943-runtime-context-helpers
fix/issue-945-owning-pr-renewal-evidence
fix/issue-941-scope-guard-bootstrap-wiring
docs/issue-930-remote-mcp-coupling-inventory
fix/issue-892-author-bootstrap-deadlock
fix/issue-686-detect-reject-manual-mcp
fix/issue-672-mcp-config-drift
fix/issue-689-deterministic-mcp-namespace
feat/issue-666-concurrent-mcp-restart-tests
feat/issue-659-maintenance-drain-mode
feat/issue-648-notifications-console
fix/issue-670-direct-master-incident
feat/issue-644-console-recovery
feat/issue-650-providers-insights
feat/issue-669-scoped-component-recovery
docs/issue-668-mcp-ha-rolling-restart
feat/issue-667-console-restart-controls
feat/issue-645-linkage-console
feat/issue-643-request-preview-initiate
fix/issue-897-permission-stale-runtime-classification
feat/issue-641-runtime-session-view
feat/issue-663-restart-classes
feat/issue-661-drain-proof-hard-gate
fix/issue-854-semantic-container-exclusion
issue-640
fix/issue-682-starlette-httpx2
v1.1.0
Labels
Clear labels
allocator
anti-stomp
architecture
bug
chore
codex
concurrency
contamination
control-plane
dashboard
database
design
documentation
enhancement
gitea
glitchtip
important
incident
incident-bridge
integration
jenkins
labels
leases
mcp
mcp-health
mcp-menu
multi-project
mutating
nice-to-have
observability
portability
preflight
protected-branch
queue
read-only
reconnect
recovery
refactor
release
reliability
resumable-review
reviewer
roadmap
safety
security
self-hosted
sentry
stale-runtime
status:blocked
status:in-progress
status:pr-open
status:ready
terminal-lock
testing
tracker
type:bug
type:feature
type:feature
type:guardrail
visibility
workflow
workflow-hardening
workflow-hardening
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
Controller-owned work allocator
Prevent concurrent LLM session stomping
Architecture / structural design
OpenAI Codex client / workflow session surface
Concurrent session safety
Workflow or session contamination incident
MCP control-plane coordination and allocation authority
MCP operational dashboard/queue view
Internal coordination storage (SQLite/Postgres)
Design / investigation, no implementation
Docs / runbooks
New feature or improvement
Gitea MCP workflow
GlitchTip integration
Operational or process incident requiring durable audit trail
Sentry-to-Gitea incident bridging
Integration testing
Jenkins integration
Label taxonomy management
Lease adopt/release/expire lifecycle
MCP server / tooling
MCP namespace and runtime health
MCP menu surface
Work spanning multiple monitoring projects or Gitea repos
Mutating action; requires gating
Observability, metrics, traces, error reporting
Cross-platform / portability
Shared preflight gates before mutation
Protected branch / stable-branch policy concern
Work queue visibility and allocation
Read-only, no mutation
MCP client reconnect/reload recovery path
Recovery paths for stale/foreign leases
Code refactor / restructure
Release / versioning
Reliability / failure handling
Persist and resume prepared review verdicts across sessions
Reviewer workflow tooling
Roadmap / umbrella issue
Safety rails and fail-closed mutation guards
Security / trust boundary
Self-hosted infrastructure integration
Sentry error monitoring integration
Stale backend daemon / runtime-vs-master parity failures
Issue is blocked
Issue is being worked on
Issue has an open pull request
Issue is ready for work
Terminal review lock (#332) path
Tests / test coverage
Issue tracker hygiene / meta
Bug or defect
Feature or enhancement
Feature or enhancement
Safety gate or guardrail
Workflow state visibility for LLMs/operators
Cross-tool workflow
LLM workflow coordination hardening
LLM workflow coordination hardening
Something is not working
This issue or pull request already exists
New feature
Need some help
Something is wrong
More information is needed
This won't be fixed
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: Scaled-Tech-Consulting/Gitea-Tools#790
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
Gitea-Tools currently uses inconsistent ownership and lease lifecycles across task types.
Reviewer and merger PR leases use short-lived ownership with activity-based renewal. Author issue-work leases can receive a fixed four-hour lease that is not shortened by heartbeat loss, process death, issue closure, or completed PR integration.
This can unnecessarily block post-PR reconciliation for hours even when the protected artifacts are independently proven safe.
Observed incident
Issue #787 and PR #789 exposed the defect:
2026-07-22T01:37:57Z.2026-07-22T05:37:57Z.safe_to_delete: true.active_issue_workwithpreserve: true.This is safe fail-closed behavior, but it creates avoidable multi-hour stalls for terminal work.
Desired design
Use one shared lease lifecycle for every actively owned task:
Task classes may have different configurable TTL and heartbeat intervals, but they must share the same acquire, renew, expire, reclaim, release, and audit semantics.
Required lease record
The shared record should include at least:
Protection rule
Active work remains protected only while all applicable conditions hold:
A stale heartbeat alone must not permit unsafe deletion. Reclaim must still use the existing cleanliness, ancestry, publication, worktree-binding, and lifecycle gates.
Terminal recovery
Add a sanctioned reconciler-only operation for terminal work.
It may reclaim an author issue-work lease before its original maximum expiry only when all required proofs succeed:
The operation must fail closed when any proof is missing or ambiguous.
Dead-session recovery
Dead-session recovery must not silently restart a complete long-duration author lease.
It should either:
The selected behavior must be documented and covered by tests.
Configuration
Centralize lease policy instead of duplicating hardcoded TTL values.
Configuration should define, per task class:
Existing active leases must have explicit compatibility behavior when configuration changes.
Concurrency safety
Use lease generations or fencing tokens so a delayed heartbeat from an older session cannot revive or overwrite ownership after another session has reclaimed the task.
Heartbeat, adoption, release, and reclaim operations must be atomic and idempotent.
Acceptance criteria
Required tests
Include tests for:
Non-goals
Relationship
Discovered during post-PR reconciliation for Issue #787 / PR #789.
Implementation sequencing with Issue #760
Issue #790 must not be implemented concurrently with Issue #760 without an explicit integration plan. Both may modify the same ownership and lease surfaces:
issue_lock_store.assess_same_issue_lease_conflictis_lease_expired(...)toward shared liveness and heartbeat semantics.Issue-lock expiry and freshness behavior
Before allocating implementation, choose one sequence:
Do not allocate #760 and #790 concurrently to separate authors unless their code boundaries and final integration order are explicitly coordinated.
Canonical Issue State
STATE: awaiting-sequencing-decision
WHO_IS_NEXT: controller
NEXT_ACTION: Choose and record the #760/#790 implementation order before allocating either issue.
NEXT_PROMPT:
WHY: Both issues modify overlapping issue-lock conflict assessment and expiry/freshness behavior. Concurrent implementation without an integration order risks textual and semantic conflicts.
BLOCKERS: Allocation is blocked only until the controller records the sequencing decision.
VALIDATION: Issues #760 and #790 are open; #790 has status:ready; overlapping implementation surfaces were verified; no implementation has been allocated.
Ledger correction — supersedes comment 13869
Corrects the
VALIDATIONfield only. The sequencing analysis and every other field in comment 13869 stand unchanged; this comment is the authoritative ledger for Issue #790.Prior wording asserted
status:readyfor #790 alone. Both issues carry it, verified live at the time of this comment.Canonical Issue State
STATE: awaiting-sequencing-decision
WHO_IS_NEXT: controller
NEXT_ACTION: Choose and record the #760/#790 implementation order before allocating either issue.
NEXT_PROMPT:
WHY: Both issues modify overlapping issue-lock conflict assessment and expiry/freshness behavior. Concurrent implementation without an integration order risks textual and semantic conflicts.
BLOCKERS: Allocation is blocked only until the controller records the sequencing decision.
VALIDATION: Issues #760 and #790 are open; both #760 and #790 have status:ready; overlapping implementation surfaces were verified; no implementation has been allocated.
Sequencing decision: Issue #760 lands first
The controller has selected the implementation order:
Issue #790 remains open and status:ready, but its implementation is intentionally sequenced behind #760. This resolves the sequencing decision previously recorded in comments 13869 and 13873.
Canonical Issue State
STATE: waiting-on-issue-760
WHO_IS_NEXT: controller
NEXT_ACTION: Hold Issue #790 until Issue #760 lands, then reassess its design and allocation readiness.
NEXT_PROMPT:
WHY: Issue #790 changes the same conflict-assessment and expiry/freshness surfaces as #760 and must build on the behavior that #760 lands first.
BLOCKERS: Issue #790 implementation is blocked until Issue #760 lands and the controller completes the post-merge reassessment.
VALIDATION: Issues #760 and #790 are open and both have status:ready; the controller selected #760 to land first; #790 is sequenced behind #760; no implementation was allocated by this comment.
[THREAD STATE LEDGER] Issue #790 — controller reassessment against the landed Issue #760 behavior; acceptance criteria amended
Scope of this comment: issue-scope amendment only. No allocation, no branch, no worktree, no lock acquisition, no implementation, no label change.
What is true now
620ed6e9a9550b8da2ceb82d9ab8744e8920490fon prgs/master, from reviewed source heada30a3ce4c37b2dde725301bef8b9ef8e04160088.~/.cache/gitea-tools/issue-locks/, no local branch matching*790*,git ls-remote prgs 'refs/heads/*790*'returns nothing, and no registered worktree matching790.Second incident: Issue #760 / PR #791
Issue #790 was filed from the Issue #787 / PR #789 incident. A second, independent instance occurred during the PR #791 post-merge reconciliation and is recorded here as further evidence.
2026-07-22T04:19:08Z,expires_at 2026-07-22T08:19:08Z; effective TTL four hours.last_heartbeat_atequal tocreated_at, never advanced across the entire authoring, review-remediation, and merge cycle.branches/fix-issue-760-exact-owner-renewalclean, on the locked branch ata30a3ce4, withgit merge-base --is-ancestor a30a3ce4 620ed6e9true and local head equal to remote head.2026-07-22T06:39:43Z— over five hours after integration completed — still returnedblocker_kind: active_branch_ownershipwith blocking categoriesauthor_lease,author_session,worktree_binding, andgitea_audit_worktree_cleanupclassified the worktreeactive_issue_workwithremovable: falseandpreserve: true.Root cause traced on master
620ed6e9issue_lock_store.assess_lock_freshnessreturnsstatus: "live"becauseexpires_athad not passed and the recorded pid is alive.gitea_mcp_server._collect_branch_ownership_records(lines 10618-10641) maps live tostatus="active"for theauthor_leaserecord and additionally emits a secondauthor_sessionrecord withstatus="active"andreclaim_allowed=False.branch_cleanup_guard.assess_ownership_record_activity(line 488) treats"active"as blocking for both records.worktree_cleanup_audit._classify(line 605) independently returnsactive_issue_workbecause the branch appears inactive_lock_branches.Decisive finding: the heartbeat field is inert.
assess_lock_freshnessparseslast_heartbeat_atatissue_lock_store.py:376-378and never branches on it; the value is only echoed into the live payload. Across the whole tree the field is written in exactly one place,gitea_mcp_server.py:2561, set tocreatedat mint time. No writer advances it. Liveness is thereforeexpires_at(issue_lock_store.py:24,WORK_LEASE_TTL_HOURS = 4) OR daemon pid liveness, and nothing else. Adding heartbeat semantics is a behavior change to one function, not a schema migration — the field is present in every durable lock on disk.Landed components to reuse rather than rebuild
reviewer_pr_lease(#747) is the working sliding-TTL reference:LEASE_TTL_MINUTES = 10,STALE_WARNING_MINUTES = 5, freshness bandsactive/stale_warning/expired, expiry re-derived from every write.issue_lock_store.lock_generationplus theexpected_generationcompare-and-swap inside the flock critical section (bind_session_lock, lines 228-241, #772). Write-path fencing exists.branch_cleanup_guard._TERMINAL_OWNERSHIP_STATUSES(line 162) containsreleased,terminal, andclosed, andassess_ownership_record_activity(line 481) returnsblocks=Falsefor them. Terminal retirement needs a status producer, not a new guard.issue_lock_renewal.assess_exact_owner_lease_renewal(#760) implements the exact-owner, clean-worktree, agreeing-heads, no-competing-claim evidence set with fail-closed refusals naming each absent element.reviewer_pr_lease.new_session_id()mints a per-task session identifier.Gaps in this issue as originally written
session IDandprocess ID, but never states that pid lifetime is not task lifetime.bind_session_locklines 200-201 writeos.getpid()into bothpidandsession_pid.assess_lock_freshnessline 394 still uses pid as an independent staleness trigger.issue_lock_worktree.assess_issue_lock_worktreebase-equivalence and the duplicate-work gate's linked-open-PR blocker.issue_lock_store.verify_lock_for_mutation(line 630) checks freshness, issue number, branch, and worktree, but notlock_generation. Fencing is write-side only; a session holding a stale in-memory lock record passes the read-side check.is_lease_expired(line 350) returns False whenexpires_atis absent, so a malformed lock is never expired. Fail-open.Amended policy: one authoritative configuration source
author_issue_workreviewer_pr/merger_prconflict_fixcontrol_plane_db.DEFAULT_LEASE_TTL_SECONDS, 4 h)Recovery grace lease is 10 minutes, one TTL, never a fresh full-duration lease. Race-drain interval before destructive cleanup is 2 minutes and configurable. Expected effect: abandoned author work becomes reclaimable in at most 10 minutes instead of four hours; work whose integration completed becomes cleanable in about 2 minutes.
Terminal retirement, and its separation from destructive cleanup
Retirement and cleanup are two distinct steps with two distinct gates.
Step 1 — record terminal status. Once every server-side proof below passes, the reconciler may atomically record terminal status immediately, through the generation compare-and-swap. There is no waiting period on this step.
merge_commit_shapresent, and the PR head SHA equal to the branch head recorded in the durable lock.git merge-base --is-ancestor.parse_dirty_tracked_filesand is on the locked branch.lock_generation, no competing live lock, no competing branch carrying the issue marker, and no other owning PR.Step 2 — destructive branch and worktree cleanup. Only after the 2-minute configurable race-drain interval has elapsed, and only after re-reading and re-proving all of:
lock_generation, terminal state, worktree cleanliness, branch containment in the target branch, and absence of competing ownership. Any drift observed on the re-read fails closed and cancels the cleanup.Owner heartbeat freshness and recorded pid liveness are audit evidence in both steps and are never retirement preconditions. Any missing or contradictory proof fails closed naming the exact absent element.
Legacy-lock compatibility
Deployment of the shorter TTL must not make existing durable locks immediately reclaimable.
expires_at. The new short TTL and missed-heartbeat grace do not apply retroactively.last_heartbeat_atequallingcreated_at. That equality is the signature of a lock that never heartbeated, and it must classify as legacy, not as fresh.Recorded pid semantics
Re-scoping the Issue #760 renewal path
The renewal disposition delivered by PR #791 stays in force until the replacement lifecycle is implemented and covered end to end. It is not removed, weakened, or short-circuited as a side effect of this work.
Under the new lifecycle the boundary moves. A lease that has crossed the missed-heartbeat reclaim threshold cannot be renewed on proof of exact historical ownership alone; historical ownership is not evidence of present activity. Such a lease must go through the sanctioned reclaim or re-adoption path, under generation fencing, which mints a new lease generation and a genuine heartbeat. Renewal remains available only to an owner that is demonstrably still heartbeating within the grace window.
Amendments to this issue's existing text
620ed6e9a9550b8da2ceb82d9ab8744e8920490f.Acceptance criteria added
AC-N1. Every actively owned task records a task/session identifier distinct from the MCP daemon process identifier, minted per task rather than per daemon. The daemon pid is recorded as evidence alongside it and is never the ownership key.
AC-N2. Recorded pid liveness is never authorization anywhere in the shared lifecycle. An alive pid never establishes freshness and never permits renewal. A dead pid may continue to corroborate staleness for the Issue #753 recovery path, whose behavior is preserved. This promotes Issue #760 AC16 from the renewal path to the shared path, and
assess_lock_freshnessmust no longer treat pid liveness as an independent liveness determinant.AC-N3. Terminal retirement of an author issue lease has no owner-liveness precondition of any kind. When the terminality, artifact-safety, and generation proofs pass, retirement proceeds whether or not the recorded pid is alive and whether or not the heartbeat is fresh. Both are recorded as audit evidence. Retirement status is recorded atomically through generation compare-and-swap with no waiting period; the configurable 2-minute race-drain interval and a full re-proof apply before destructive branch or worktree cleanup, not before recording retirement.
AC-N4.
verify_lock_for_mutationverifieslock_generationin addition to its existing freshness, issue, branch, and worktree checks, so a session holding a stale in-memory lock record cannot pass the read-side gate after another session has reclaimed or retired the work.AC-N5. The Issue #760 exact-owner renewal path is re-scoped to require a fresh heartbeat within the grace window. A lease past the missed-heartbeat reclaim threshold is not renewable on historical ownership evidence alone and must use the sanctioned reclaim or re-adoption path under generation fencing. The remediation delivered by PR #791 remains in force until the replacement lifecycle is implemented and covered end to end.
AC-N6. Native MCP integration tests are required for every relevant downstream gate, driving the real tools against a real repository and a real durable lock — not only the pure assessors. Coverage must include each gate that consumes a sanction or waiver, at minimum
issue_lock_worktree.assess_issue_lock_worktreebase-equivalence and the duplicate-work linked-open-PR gate, plus every new gate this work introduces. Assessor-only coverage is explicitly insufficient: it did not catch either of the two sanction discard points found in review #499 on PR #791.AC-N7. All durations — initial TTL, heartbeat cadence, stale-warning threshold, missed-heartbeat grace, absolute cap, recovery grace, and race-drain interval — come from a single authoritative policy configuration source, introduced in the first implementation slice so that the first heartbeat and TTL behavior to ship reads from it. No duration is hardcoded at a call site, and no duration is introduced in an earlier slice than the configuration that owns it.
AC-N8. Legacy locks written before task-session heartbeats exist retain their recorded absolute expiry on deployment and do not become immediately reclaimable. They leave that state only by qualifying for terminal retirement or by canonical exact-owner rebinding into the new lifecycle. A fresh heartbeat is never inferred from
last_heartbeat_atequallingcreated_at. The disposition is recorded durably and covered by tests in both directions.Implementation slices
Slice A — shared policy plus load-bearing heartbeat. Introduce the central policy configuration module first, so the first shipped heartbeat and TTL behavior reads from one authoritative source; no durations are introduced ahead of it. Make the heartbeat load-bearing in
assess_lock_freshnesswith a newstale_missed_heartbeatband. Add the heartbeat writer under generation compare-and-swap. Moveauthor_issue_workfrom the four-hour absolute TTL to the sliding TTL from the policy table. Mint per-task session identifiers and demote the recorded pid to evidence. Implement the AC-N8 legacy disposition in the same slice so no deployed lock is retroactively shortened. Satisfies AC-N1, AC-N2, AC-N7, AC-N8.Slice B — terminal retirement. Add the terminal-state assessor and the reconciler-only retirement operation, reusing the Issue #760 evidence gatherer. Record terminal status atomically through the compare-and-swap, producing a status the existing
_TERMINAL_OWNERSHIP_STATUSESpath consumes without modification tobranch_cleanup_guard. Add the race-drain interval and the mandatory re-proof before destructive cleanup. Satisfies AC-N3.Slice C — fencing completion and renewal re-scope. Add the
lock_generationcheck toverify_lock_for_mutation. Re-scope the Issue #760 renewal path per AC-N5. Extend the shared lifecycle to the remaining task classes and align the control-plane lease TTL. Satisfies AC-N4, AC-N5.AC-N6 applies across all three slices and is not deferrable to the last one.
Dependencies
Landed and required as the baseline: Issue #760 via PR #791 at merge commit
620ed6e9a9550b8da2ceb82d9ab8744e8920490f(renewal assessor, reused); Issue #772 (generation compare-and-swap); Issue #747 (sliding TTL model); Issue #753 (dead-session recovery, behavior preserved); Issue #755 (owning-PR evidence pattern); Issue #601 (lease lifecycle and expired-lock reclaim). Incident evidence: Issue #787 / PR #789, and Issue #760 / PR #791. Conflict surface: any work touchingassess_same_issue_lease_conflictor theissue_lock_storeexpiry path; none is allocated at the time of this comment.What changed
What is blocked
Who/what acts next
assess_same_issue_lease_conflictor theissue_lock_storeexpiry path. Do not remove or weaken the PR #791 renewal remediation before the replacement lifecycle is covered end to end.Canonical Issue State
STATE: reassessed-pending-allocation
WHO_IS_NEXT: controller
NEXT_ACTION: Decide whether to allocate Issue #790, beginning with Slice A as a separately bounded author task.
NEXT_PROMPT:
WHY: The Issue #760 / PR #791 reconciliation produced a second instance of the defect this issue exists to correct, and reading the landed code showed the heartbeat field is parsed but never consulted, so the original acceptance criteria needed concrete durations, a corrected terminal-retirement rule, explicit pid semantics, a legacy compatibility rule, and mandatory native MCP integration coverage.
ISSUE: #790
RELATED_PRS: None for this issue; it remains unallocated. Baseline is Issue #760 via PR #791 at merge commit
620ed6e9a9.BLOCKERS: None. Allocation is a controller decision, not a blocked state.
VALIDATION: Identity sysadmin, profile prgs-reconciler, role reconciler, session bound to Scaled-Tech-Consulting/Gitea-Tools on prgs at gitea.prgs.cc, identity_match true. Server-implementation parity in_parity true at
620ed6e9a9with stale false and restart_required false; control checkout on master with a zero-entry git status --porcelain. Issue #790 read live and confirmed to be in the open state with its label set unchanged before and after this comment. Allocation state verified unallocated by four independent checks: no durable lock file for issue 790, no local branch matching 790, git ls-remote prgs 'refs/heads/790' empty, and no registered worktree matching 790. Landed behavior read directly from master at620ed6e9: issue_lock_renewal.py in full, issue_lock_store.py assess_lock_freshness, assess_expired_lock_reclaim, assess_same_issue_lease_conflict, bind_session_lock, lock_generation and verify_lock_for_mutation, branch_cleanup_guard.assess_ownership_record_activity and assess_active_branch_ownership, worktree_cleanup_audit classification, reviewer_pr_lease TTL constants, and the gitea_mcp_server author-lease builder and ownership-record collector. A tree-wide search confirmed last_heartbeat_at is written only at gitea_mcp_server.py:2561 and read only at issue_lock_store.py:376-378, with no writer advancing it. Preflight order gitea_whoami then gitea_resolve_task_capability with task comment_issue and nothing in between preceded this comment; allowed_in_current_session true. No lock was acquired, no branch or worktree was created, no label was changed, and no implementation was begun by this controller action.LAST_UPDATED_BY: sysadmin / prgs-reconciler (controller action)
[THREAD STATE LEDGER] Issue #790 — scope transfer: this issue now tracks Slice A only; Slices B and C move to #792 and #793
What is true now
fix/issue-790-slice-a-heartbeat-policyfrom worktreebranches/issue-790-slice-a-heartbeat-policy. Slice A is implemented, exercised against the full test suite and a clean master baseline worktree, committed at243f52dc7959c362242ebea321fafa3950f13083, and pushed. The pull request is opened immediately after this comment.Why the transfer
The enforced create-PR gate at
gitea_mcp_server.py:4684fails closed unless the pull-request title or body containsCloses #790orFixes #790exactly. The repository therefore has no partial-slice pull-request tracking: any pull request for this issue must carry a closing reference, and merging it will close this issue.docs/llm-workflow-runbooks.md:550documentsImplements #NandRefs #Nas non-closing forms, but the gate does not accept them for the locked issue.Rather than merge a pull request that closes an issue with two thirds of its scope unbuilt, the remaining scope has been transferred to dedicated issues before the pull request is opened. Closing Issue #790 on merge is then correct, because the work it no longer tracks lives in #792 and #793.
Explicit statements required by this transfer
Transferred scope
#792 — Slice B, terminal retirement. Terminal author-lease retirement; a reconciler-only retirement operation; terminal status recorded atomically through the
lock_generationcompare-and-swap; the configurable two-minute race-drain interval; full revalidation of generation, terminal state, cleanliness, containment, and ownership before destructive branch or worktree cleanup; native MCP integration coverage. Carries AC-N3 from comment 13958 as AC-B1, and the corrected proof list with the owner-liveness conjunct struck.#793 — Slice C, fencing completion and task-class alignment.
lock_generationverification inverify_lock_for_mutation; the Issue #760 renewal re-scope under heartbeat and fencing rules; remaining task-class policy alignment for reviewer, merger, and conflict-fix leases; control-plane lease alignment; native MCP integration coverage. Carries AC-N4 and AC-N5 from comment 13958 as AC-C1 and AC-C2.AC-N6, the native MCP integration requirement, applies to all three slices and is restated in each follow-up issue.
What changed
What is blocked
Who/what acts next
Canonical Issue State
STATE: implementation-complete
WHO_IS_NEXT: reviewer
NEXT_ACTION: Review the Slice A pull request at head
243f52dc79.NEXT_PROMPT:
WHY: The enforced create-PR gate requires a closing reference for the locked issue, so the scope Issue #790 no longer delivers was transferred to dedicated issues before the pull request was opened.
ISSUE: #790
RELATED_PRS: The Slice A pull request, opened immediately after this comment at head
243f52dc79. Follow-ups #792 and #793 have no pull request and are unallocated.BLOCKERS: None.
VALIDATION: Identity jcwalker3, profile prgs-author, role author, bound to Scaled-Tech-Consulting/Gitea-Tools on prgs at gitea.prgs.cc, identity_match true. Issue #792 and Issue #793 created through native gitea_create_issue with workflow label validation reporting valid true for both. Preflight order gitea_whoami then gitea_resolve_task_capability with the exact task name and nothing in between preceded each of the two issue creations and this comment. The gate wording requiring a closing reference was read directly from gitea_mcp_server.py line 4684, and the non-closing forms documented at docs/llm-workflow-runbooks.md line 550 were confirmed not to satisfy it.
LAST_UPDATED_BY: jcwalker3 / prgs-author (author session)