gitea_bootstrap_author_issue_worktree wrote a lock no downstream author
operation accepts, then directed the author straight to implementation. Once
the branch carried commits, heartbeat, re-lock, exact-owner renewal, and the
#447 create-PR guard all refused simultaneously and no sanctioned recovery
path remained eligible.
Each of those gates is individually correct. The defect was that two writers
disagreed about what a lock is.
- Add author_lock_contract as the single canonical definition: claimant,
work_lease, lock_provenance, generation, and an explicit expiration state.
Both gitea_lock_issue and bootstrap now build through it.
- Promote issue_lock_store.lock_claimant to the one shared claimant reader and
use it in the ownership check, so a claimant recorded at the lock top level
is read rather than refused. The values are still compared against
server-resolved identity and profile, so no legacy placement grants anything
the canonical placement would not.
- Represent missing expiration explicitly. An absent expires_at previously read
as "not yet expired", leaving a malformed lock permanently non-expiring and
permanently ineligible for #760 renewal.
- Bootstrap reads its lock back and verifies it structurally before reporting
success. A partial lock fails closed while the worktree is still
base-equivalent, names the missing fields, and never reports
implementation_allowed. Its exact_next_action now matches the state returned.
- Add gitea_recover_incomplete_bootstrap_lock for locks already written by the
old bootstrap, including those whose branches carry pushed commits. It never
moves, resets, or rewinds a branch, never requires base-equivalence, never
pushes or opens a PR, and touches only the target lock. It proves repository,
issue, claimant username and profile, branch, worktree, registration, and
head before writing, refuses healthy foreign-owned locks, and mints
provenance and authorization server-side.
- Add gitea_inspect_issue_lock_contract, a strictly read-only surface.
- Document the required ordering and the recovery path.
The #447 provenance guard is unchanged and the sanctioned source set was not
widened: bootstrap now satisfies the guard rather than the guard being relaxed
to admit bootstrap.
Tests: 61 new cases covering the canonical schema, immediate heartbeat,
renewal before and after commits, the create_pr guard, executable next actions,
partial and malformed and missing-expiration and expired and same-owner and
foreign-owner and legacy locks, recovery isolation, read-only inspection, and
the full bootstrap-implement-commit-push-create_pr regression against isolated
fixtures.
Full suite at head: 28 failed, 5753 passed, 6 skipped, 1042 subtests.
Full suite at base 82d71b77: 28 failed, 5692 passed, 6 skipped, 1042 subtests.
Failing test-ID sets are identical, so there are zero regressions; the +61
passes are this issue's new suite.
Issue #949 was preserved and not recovered: its branch remains at
92615f474b and its worktree, lock, and PR state
were not touched. The #949-shaped regression uses isolated fixtures only.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Align assess_author_issue_bootstrap with bootstrap_permits_control_checkout
so gitea_bootstrap_author_issue_worktree can create the first branches/
worktree without the lock↔worktree deadlock.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- Remove /branches/ string-split fallback in resolve_canonical_repo_root;
recover roots via commonpath ancestry only (review #531 F2).
- Refuse existing branches that do not contain live master; no weak
merge-base acceptance (F3).
- Verify caller-supplied assignment_id/lease_id against the control plane
or fail closed (F4).
- Compensating recovery releases bound workflow leases via lease_lifecycle (F5).
- Regression tests for each finding.