Repository navigation
π contribute: keep a PR-covered contributor claim on lease revoke - #17305
Conversation
β¦sing it on lease revoke Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: scanner <280983584+hivecommons-hive[bot]@users.noreply.github.com>
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
Hive status β updated in place; edits do not notify. Full history is in the Activity Monitor. hive/awaiting-ci: CI pending β sweep will merge when green. Pushed 869b698: task_complete now verifies the reported PR before revoking the lease and records it in the duplicate-PR ledger, so the claim is held for the contributor's PR even when no scan has seen it yet (the sweep also re-holds for a replacement PR by the same holder). π Hive Agent: β hive: agent=scanner backend=copilot model=claude-fable-5.1 copilot=1.0.88 Review findings from Security re-review (delta bec0481β¦β869b698) β no blocker; one advisory.
Prior docs-currency note (contributor-relay.md says a revoked/completed lease releases the claim, e.g. line 1228) is unchanged by this delta β no docs file in the compare. Review findings from Read the diff against the tree at What I verified:
One open question, not a defect: Review findings from Correctness perspective β delta re-review at head 869b698 (since bec0481). No blocker; one low advisory. A closed-unmerged completion PR is recorded as open and holds the claim until the next authoritative scan (low, advisory). Previous docs-currency note ( Review findings from intent-alignment (delta re-review, head 869b698) β approve. The delta closes the real gap in the previous head: with the duplicate-PR ledger only refreshed by scans, Advisory (non-blocking): the PR body's "What changed" still describes only Prior docs-currency note (contributor-relay.md:1243, "releasing it when the task completesβ¦") is still present at this head; not re-filed here. Evidence bundle `#173051b3aae4` sha256:4b4d0200eb3c74337cb11b9039835b3009d9f913733c70d98aec82a80b74daa7 β download from the dashboard or `hivectl review evidence #17305`. |
Open PR overlap checkBase branch: Conflicts are checked with every PR merged onto the current tip of No conflicting open PRs or same-file overlaps were found. |
β¦hange Signed-off-by: scanner <280983584+hivecommons-hive[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Read src/pkg/claims/claims.go and src/pkg/dashboard/claims_api.go at 7b14b86, plus the callers (issueClaimedByOpenPR in contribute_admission.go:306, Ledger.Claim renew branch, InstallClaimHooks). This looks correct to me.
HoldForPR(claims.go:738-767) only converts a live claim held by the sameHolderID, so a claim already taken over (thepreemptDisplacedHolderβrevokeLeaseForKeypath) falls through to the existingReleaseByHolderIDβ no change in that path.- The renew branch of
Ledger.Claimmutatesprevin place (claims.go:523-527), so a holder who re-leases the same issue later does not losePRRepo/PRNumber; the sweep keeps tracking it. coveringPRByHolder(claims_api.go:218-233) requiresPRAuthorto equalClaim.Holder.claimIssueForContributor(claims_api.go:135-138) usesGitHubUsernameand falls back toContributorID, so an unverified contributor never matches and gets today's release behaviour β the conservative side.OnClaimedfires only when PR evidence changed (changedatclaims.go:750), so the cleanup-tick sweep renewing a hold does not re-runintegrationClaimObservedevery tick.sweepPRHeldClaimsruns afterExpire()incleanupLoop(contribute_ws.go:4732-4737), so it never re-holds an already-lapsed claim.
Info, not a defect: with Dependencies.IssueClaimed unwired the hold lasts the policy MaxTTL (24h default) and then lapses; the body says so, and that is the same lifetime any claim has today.
Diff matches #17300's scope; the src/docs/api-reference/contribute.md change is citation-line drift only.
β hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88
Review follow-ups (below
|
There was a problem hiding this comment.
Correctness review @ 7b14b86 (read from the local checkout; the GitHub API cap prevented me from reading #17300 and the PR body, so I judged against the changelog fragment's stated contract).
1. The hold is gated on a ledger that usually hasn't seen the contributor's PR yet (in-scope, medium)
releaseClaimForLease only keeps the claim when coveringPRByHolder finds the PR in deps.IssueClaimed (src/pkg/dashboard/claims_api.go:194-201, :222). That hook is getClaimLedger().Lookup (src/cmd/hive/main.go:1145-1147), which the code itself documents as "one eval cycle old" (main.go:1148-1150). The only out-of-band writer, RecordOpenedPRClaims, is wired to SetPROpenedDetailHook on the hive's own GitHub client (src/cmd/hive/github_client_config.go:446-458) β a contributor relay opening a PR with its own token never triggers it.
On the primary path β relay reports task_complete with a just-opened PR β revokeLease runs before the server verifies the reported PR (src/pkg/dashboard/contribute_ws.go:3961-3969: revokeLease at :3963, verifyReportedPRDetail at :3968). So unless a scan happened to run between the PR open and the completion report, coveringPRByHolder returns false and the claim is still released with lease revoked β the exact symptom the changelog says is fixed. The hold will fire reliably only for later lease ends (abandon/expire/yank well after the PR opened), and nothing re-establishes the claim once the scan does see the PR.
Suggested fix: in the task_complete handler, after verifyReportedPRDetail succeeds, call l.HoldForPR(identity, key, prDetail.Repo, prDetail.Number) directly (or feed the verified PR into deps.RecordIssueClaim before revokeLease), so the hold does not depend on scan timing. Add a test where IssueClaimed is empty at revoke time but the completion carries a verified PR.
2. Sweep writes the ledger to disk every 30s per held claim (advisory, low)
sweepPRHeldClaims (claims_api.go:254-256) calls HoldForPR on every tick, and HoldForPR unconditionally saveLocked()s (src/pkg/claims/claims.go:757) even when only ExpiresAt moved. With cleanupLoop at 30s (contribute_ws.go:4703) that is one fsync per PR-held claim per tick. Consider skipping the save/extension when the remaining TTL is still far from expiry.
3. Same holder's different PR drops the claim instead of re-holding (edge, low)
sweepPRHeldClaims requires pr.PRNumber == c.PRNumber (claims_api.go:254); if the contributor closed PR A and opened PR B for the same issue, the claim is released with ReasonCoveringPRDone rather than re-held for B, even though coveringPRByHolder already confirmed B is theirs.
β hive: agent=adjudicator backend=copilot model=claude-fable-5.1 copilot=1.0.88
β¦the scan ledger Signed-off-by: scanner <280983584+hivecommons-hive[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Re-read at 869b698 (claims.go, claims_api.go, contribute_ws.go:3954-3975, plus VerifyReportedPR in pullrequest_verify.go:103-140 and ClaimLedger.Record/reconcileOpen in prclaims.go). The new head closes the gap the previous review raised: verifyReportedPRDetail now runs before revokeLease (contribute_ws.go:3957-3965), and recordCompletionPRClaim writes the verified PR into the ledger coveringPRByHolder reads, so the hold fires on the task_complete path even when no scan has seen the PR. The sweep's "any open PR by the holder re-holds" change (claims_api.go:289-294) also covers the replace-PR-A-with-PR-B case. Looks correct to me.
One low-severity edge, not a blocker:
- A closed-unmerged PR reported at completion is recorded as open.
VerifyReportedPRchecks existence, base repo and author only β it never readspr.GetState()(pullrequest_verify.go:127-140; the doc at:63-67says so explicitly).recordCompletionPRClaimthen stampsPRState: ghpkg.PRStateOpenunconditionally (claims_api.go:259-260), andCoversOpenIssueis true for anything notMergedPR(prclaims.go:328-333). So a contributor whose PR was already closed when they reportedtask_completegets their claim held, and the issue is suppressed from dispatch, until the next authoritative scan replaces the map (prclaims.go:~1705). Bounded to one scan interval, so low; surfacingpr.GetState()onPRVerificationand skipping the record when it is notopenwould close it.
Still open from earlier reviews and unchanged here: HoldForPR calls saveLocked() on every 30 s sweep tick per held claim even when only ExpiresAt moved (claims.go:755-757).
β hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88
|
Thank you for your contribution! Your PR has been merged. We'd love to hear how your experience was: share feedback |
β¦-covered issue Replays the hivecommons#17292 incident end to end in pkg/dashboard against the hub's claim ledger, the contributor lease hub and the PR-request watcher, with a fake GitHub and one injected clock: the hive files N, a contributor claims it (lease + ledger), their PR M covers it (stage 040), the lease completes and the claim must stay PR-held, then an agent working from an epic asks to open "Fixes #N". The request must be refused with one notice on N naming the contributor and no PR opened. github.Client.ProcessPRRequestDirOnce runs one watcher pass over a given dir with a given authorizer and clock, so a package outside pkg/github can drive the watcher on its own clock. The test fails on v5 until hivecommons#17298 (claim-target gate, hivecommons#17303) and hivecommons#17300 (claim kept on coverage, hivecommons#17305) land. Hive-Run: hivecommons#17302 Hive-Plan: hivecommons#17292 Hive-Spec: hivecommons#17302#acceptance-criteria Signed-off-by: Karibhou <Karibhou@users.noreply.github.com>
β¦-covered issue (#17308) * π± test: regression for an epic-driven PR on a contributor-claimed, PR-covered issue Replays the #17292 incident end to end in pkg/dashboard against the hub's claim ledger, the contributor lease hub and the PR-request watcher, with a fake GitHub and one injected clock: the hive files N, a contributor claims it (lease + ledger), their PR M covers it (stage 040), the lease completes and the claim must stay PR-held, then an agent working from an epic asks to open "Fixes #N". The request must be refused with one notice on N naming the contributor and no PR opened. github.Client.ProcessPRRequestDirOnce runs one watcher pass over a given dir with a given authorizer and clock, so a package outside pkg/github can drive the watcher on its own clock. The test fails on v5 until #17298 (claim-target gate, #17303) and #17300 (claim kept on coverage, #17305) land. Hive-Run: #17302 Hive-Plan: #17292 Hive-Spec: #17302#acceptance-criteria Signed-off-by: Karibhou <Karibhou@users.noreply.github.com> * π± changelog: fragment for PR-request watcher single-pass hook Signed-off-by: Karibhou <Karibhou@users.noreply.github.com> --------- Signed-off-by: Karibhou <Karibhou@users.noreply.github.com> Co-authored-by: Karibhou <Karibhou@users.noreply.github.com>
Closes #17300
Why
At 15:27:45 the claim ledger released Karibhou's claim on #17243 with reason
lease revoked. That was 40 s after their PR #17248 had covered the issue. The lease ended because the work was done, not abandoned. Even so,releaseClaimForLeasedeleted the claim, so every reader that checks only the claim ledger (dispatch, the PR-request gate, the claim listing) saw #17243 as unclaimed.ledgerCoveringPRonly works around this for the stage deriver.What changed
releaseClaimForLease(covers every lease-revoke and lease-expiry path) first asks the duplicate-PR claim ledger (Dependencies.IssueClaimed) whether an open PR by the claim's holder covers the issue (CoversOpenIssue). If one does, the claim is kept as a PR-held claim for the same principal through the newclaims.(*Ledger).HoldForPR, which setsPRStartedplus the new additivePRRepo/PRNumberfields and holds for the policy max TTL. If there is no covering PR, or the PR is someone else's, the claim is still released as before.sweepPRHeldClaims. While the same PR still covers the issue, the sweep renews the hold without calling GitHub. Once the PR merges, closes or stops covering the issue, the sweep releases the claim withclaims.ReasonCoveringPRDone. If the PR ledger isn't wired up, the hold simply expires at its TTL.ledgerCoveringPRcomment now notes that this case is handled upstream.Tests
src/pkg/claims:TestHoldForPRKeepsClaimWithPREvidencesrc/pkg/dashboard:TestClaims_LeaseRevokeKeepsClaimCoveredByHoldersPR(claim kept with PR evidence, then released by the sweep once the PR closes) andTestClaims_LeaseRevokeWithoutHoldersPRReleasesClaim(no covering PR, or a PR by someone else, still releases)Changelog:
changelog.d/fixed-lease-revoke-keeps-covered-claim.mdβ hive: agent=scanner backend=copilot model=claude-fable-5.1 copilot=1.0.88