Implementer sessions: limit "one pull request" to open pull requests #206
Labels
No labels
Agent
Ignore
Agent
In Progress
Agent
Planned
Agent
Planning
Agent
Ready
Agent
Triage
Compat
Breaking
Kind
Bug
Kind
Documentation
Kind
Enhancement
Kind
Feature
Kind
Security
Kind
Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
lucas/omp-deck#206
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Forgejo implementer sessions refuse
forgejo_create_prwith "this session already opened pull request #N; push further commits to its branch instead" as soon as any PR is mapped to the session (forgejo_sessions). The only exception is the newest PR having afailedpost-merge CI record (followUpFor). After a session's PR is merged or closed, a legitimate follow-up PR is therefore blocked. The rule must count only PRs that are still open. The same applies to the companiongit_branchrestriction (a new PR needs a new branch) and to the system-prompt wording. The deck stores no PR open/closed state, so the check queries Forgejo live at call time.Plan
Context
In
apps/server/src/bridge/session-policy.ts,forgejoImplementerPolicy.decide(~L436–441) and thegit_branchcheck (~L443–449) gate onimplementerPrNumbersplusfollowUpFor.followUpForis the failed-post-merge-CI exception. Target end state: both gates allow the call once every PR mapped to the session is merged or closed, as reported live by Forgejo. They deny while any mapped PR is open.followUpForand its prompt bullet go away, because "no open PR" subsumes that case.Approach
apps/server/src/forgejo/dispatch-api.ts:state?: stringtoRawPull./** "closed" also covers merged PRs. */ state: "open" | "closed";toPullSummary.readPullmap it asstate: raw.state === "closed" ? "closed" : "open". This mirrors theIssueSummarymapping at ~L338.PullSummaryliteral in tests:grep -rn "mergeCommitSha:" apps/server/src --include=*.test.ts, in particular the fake inforgejo/dispatcher.test.ts. Addstate: "open"to each.session-policy.ts:SessionPolicy.decide's return type toPolicyDecision | Promise<PolicyDecision>.api: () => DispatchApi | nulltodecideand todecidePolicy.decidePolicyasync, returningPromise<PolicyDecision>, andawaitthe per-kinddecide. Importtype DispatchApifrom../forgejo/dispatch-api.ts.followUpForwith a live open check.followUpForand thegetPostMergeimport (it is the only user in this file).async function openPrDenial(scope: ForgejoImplementerScope, sessionId: string, api: () => DispatchApi | null, signal?: AbortSignal): Promise<{ deny: string } | null>. It returnsnullwhenimplementerPrNumbers(scope, sessionId)is empty. Otherwise it getsconst client = api():nullclient →{ deny: "cannot check whether this session's pull requests are still open: Forgejo is not configured" }.await client.readPull(scope.owner, scope.repo, n, signal).{ deny: \could not check whether pull request #${n} is still open: ${res.status} ${res.message}` }`.state === "open"→{ deny: \pull request #${n} of this session is still open; push further commits to its branch instead` }`.null.forgejoImplementerPolicy.decideasyncand addapi(and passsignal, see step 4):forgejo_create_pr:const denial = await openPrDenial(...); if (denial) return denial;. This replaces the old block.git_branch(insidescope.headBranch !== undefined): keepaction !== "list" && !onHead, but replacefollowUpFor(...) === nullwith a call toopenPrDenial. If it returns a denial, return{ deny: \only the pull request's head branch ${scope.headBranch} may be created or switched to while a pull request of this session is open (${denial.deny})` }`. Otherwise fall through.git_push(~L450–452) to say the follow-up branch becomes reachable once the session's PRs are all merged or closed.apps/server/src/bridge/permission-filter.ts:forgejoApi?: () => DispatchApi | null, defaulting tocreateDispatchApifrom../forgejo/dispatch-api.ts.evaluatePermission, callawait decidePolicy(this.trigger, toolName, params, this.sessionId, this.cwd, this.forgejoApi, signal). Addsignal?: AbortSignalas the last parameter ofdecidePolicyanddecideafterapi, and pass it toopenPrDenial.in-process.ts~L943. It needs no change, because the default applies.forgejoImplementerPolicy.systemContext):followUpandfollowUpRuleand every spread offollowUpRule.onePrunconditionally to" While it is open, do not open another pull request; once it is merged or closed you may open a follow-up pull request from a new branch."maintain's first bullet, replace the conditional" You cannot use other branches."with" Other branches are available only once the pull request is merged or closed."Critical files & anchors
apps/server/src/bridge/session-policy.ts:followUpFor,forgejoImplementerPolicy.decide/systemContext,decidePolicy.apps/server/src/bridge/permission-filter.ts:evaluatePermission(~L242), constructor options.apps/server/src/forgejo/dispatch-api.ts:RawPull,PullSummary,readPull.apps/server/src/bridge/session-policy.test.ts: everydecidePolicy(call becomesawait decidePolicy(with anapiargument, and the test functions becomeasync.Verification
Work in
apps/server.session-policy.test.ts. Add a fakeapihelper returning an object whosereadPullanswers from aMap<number, "open" | "closed">(other methods unused; cast). Pass() => nullwhere Forgejo is irrelevant."allow"."pull request #12 of this session is still open; push further commits to its branch instead"."allow".readPullreturning{ ok: false, status: 500, message: "boom" }→ deny"could not check whether pull request #12 is still open: 500 boom".api: () => nullwith a mapped PR → deny mentioning "Forgejo is not configured".insertPostMerge:headBranch: "agent/x"and #12 open,git_branchcreate"other"→ deny."allow", and create_pr →"allow"."While it is open, do not open another pull request"and no"Open exactly one follow-up pull request".bun test src/bridge/session-policy.test.ts src/bridge/permission-filter.test.ts src/forgejo/dispatcher.test.ts src/db/forgejo-post-merge.test.ts. All must pass.bun x tsc --noEmit -p .(or the repo'stypecheckscript). It must be clean.Assumptions & contingencies
readPullper mapped PR. A Forgejo outage blocks new PRs (fail closed). This is intended.PermissionFilterturns out to have other callers ofdecidePolicy(checkgrep -rn "decidePolicy(" apps/server/src), make themawaitit and passcreateDispatchApi.