Files
orca/src/main/runtime/runtime-gitlab-mutation-commands.ts
T
Neil 12bf32836b feat(ci): ratchet raw repo connectionId host checks; migrate the cheap ones (#31096)
* feat(ci): ratchet raw repo connectionId host reads; one drop-in resolver

Codex review 6 class fix (#4/#6/#7): host decisions read off a repo row's
legacy connectionId miss rows that name SSH only as executionHostId.

- getRepoRoutingConnectionId (shared/execution-host.ts): drop-in for a raw
  repo.connectionId routing check that also finds executionHostId-only SSH.
- check-repo-host-read-ratchet: per-file count of raw repo connectionId
  reads in src/main, src/shared, src/cli; counts may only shrink.
- Migrated 66 cheap reads: runtime GitHub/GitLab commands, PR/MR head ref
  fetches, preserved-branch cleanup, ref queries, base-ref IPC, worktree
  preparation, trash sweep, scan cache TTL, usage metadata.

* fix(ipc): PR base IPC routes executionHostId-only SSH rows; ratchet is a stated lower bound

Review follow-up (Opus on #31096):
- worktrees:resolvePrBase decided git/gh routing on the raw connectionId while
  its head-ref fetches already used the resolver; one resolved connectionId
  now drives all of it (test fails on main).
- getRepoRoutingConnectionId's contract is stated exactly: never local when the
  raw field is set; on a contradictory ssh row executionHostId wins.
- The ratchet regex no longer matches report.connectionId; header and docs
  say it is a lower bound.
- Regression tests for a runtime GitHub and GitLab command site.

* test(ci): pin the repo host-read ratchet step to the static phase

* fix(ci): repo host-read ratchet skips verbatim field copies into projected rows

A copy of connectionId into a projected row decides nothing; migrating it to
the resolver would change what the projection publishes. The sidebar feed
catalog's copy (added after the baseline) and two older projections no longer
count; baseline pruned to 91.
2026-10-20 14:00:32 -07:00

243 lines
5.7 KiB
TypeScript

import type {
GitLabIssueUpdate,
GitLabMRInlineCommentInput,
GitLabMRUpdate,
GitLabProjectRef
} from '../../shared/gitlab-types'
import type { Repo } from '../../shared/repo-types'
import {
addIssueComment,
addMRComment,
addMRInlineComment,
closeMR,
createIssue,
getJobTrace,
mergeMR,
reopenMR,
resolveMRDiscussion,
retryJob,
updateIssue,
updateMR,
updateMRReviewers
} from '../gitlab/client'
import { getRepoRoutingConnectionId } from '../../shared/execution-host'
type LocalGitArgs = [] | [{ wslDistro?: string }]
export class RuntimeGitLabMutationCommands {
constructor(
private readonly deps: {
resolveRepo: (selector: string) => Promise<Repo>
getLocalGitArgs: (repo: Repo) => LocalGitArgs
}
) {}
async createGitLabRepoIssue(repoSelector: string, title: string, body: string) {
const repo = await this.deps.resolveRepo(repoSelector)
return createIssue(
repo.path,
title,
body,
repo.issueSourcePreference,
getRepoRoutingConnectionId(repo),
...this.deps.getLocalGitArgs(repo)
)
}
async updateGitLabRepoIssue(
repoSelector: string,
number: number,
updates: GitLabIssueUpdate,
projectRef?: GitLabProjectRef | null
) {
const repo = await this.deps.resolveRepo(repoSelector)
return updateIssue(
repo.path,
number,
updates,
repo.issueSourcePreference,
getRepoRoutingConnectionId(repo),
projectRef,
...this.deps.getLocalGitArgs(repo)
)
}
async addGitLabRepoIssueComment(
repoSelector: string,
number: number,
body: string,
projectRef?: GitLabProjectRef | null
) {
const repo = await this.deps.resolveRepo(repoSelector)
return addIssueComment(
repo.path,
number,
body,
repo.issueSourcePreference,
getRepoRoutingConnectionId(repo),
projectRef,
...this.deps.getLocalGitArgs(repo)
)
}
async addGitLabRepoMRComment(
repoSelector: string,
iid: number,
body: string,
projectRef?: GitLabProjectRef | null
) {
const repo = await this.deps.resolveRepo(repoSelector)
return addMRComment(
repo.path,
iid,
body,
repo.issueSourcePreference,
getRepoRoutingConnectionId(repo),
projectRef,
...this.deps.getLocalGitArgs(repo)
)
}
async addGitLabRepoMRInlineComment(
repoSelector: string,
iid: number,
input: GitLabMRInlineCommentInput,
projectRef?: GitLabProjectRef | null
) {
const repo = await this.deps.resolveRepo(repoSelector)
return addMRInlineComment(
repo.path,
iid,
input,
repo.issueSourcePreference,
getRepoRoutingConnectionId(repo),
projectRef,
...this.deps.getLocalGitArgs(repo)
)
}
async resolveGitLabRepoMRDiscussion(
repoSelector: string,
iid: number,
discussionId: string,
resolved: boolean,
projectRef?: GitLabProjectRef | null
) {
const repo = await this.deps.resolveRepo(repoSelector)
return resolveMRDiscussion(
repo.path,
iid,
discussionId,
resolved,
repo.issueSourcePreference,
getRepoRoutingConnectionId(repo),
projectRef,
...this.deps.getLocalGitArgs(repo)
)
}
async getGitLabRepoJobTrace(
repoSelector: string,
jobId: number,
projectRef?: GitLabProjectRef | null
) {
const repo = await this.deps.resolveRepo(repoSelector)
return getJobTrace(
repo.path,
jobId,
repo.issueSourcePreference,
getRepoRoutingConnectionId(repo),
projectRef,
...this.deps.getLocalGitArgs(repo)
)
}
async retryGitLabRepoJob(
repoSelector: string,
jobId: number,
projectRef?: GitLabProjectRef | null
) {
const repo = await this.deps.resolveRepo(repoSelector)
return retryJob(
repo.path,
jobId,
repo.issueSourcePreference,
getRepoRoutingConnectionId(repo),
projectRef,
...this.deps.getLocalGitArgs(repo)
)
}
async mergeGitLabRepoMR(
repoSelector: string,
iid: number,
method?: 'merge' | 'squash' | 'rebase',
projectRef?: GitLabProjectRef | null
) {
const repo = await this.deps.resolveRepo(repoSelector)
return mergeMR(
repo.path,
iid,
method ?? 'merge',
repo.issueSourcePreference,
getRepoRoutingConnectionId(repo),
projectRef,
...this.deps.getLocalGitArgs(repo)
)
}
async updateGitLabRepoMRState(
repoSelector: string,
iid: number,
state: 'opened' | 'closed',
projectRef?: GitLabProjectRef | null
) {
const repo = await this.deps.resolveRepo(repoSelector)
const updateState = state === 'closed' ? closeMR : reopenMR
return updateState(
repo.path,
iid,
repo.issueSourcePreference,
getRepoRoutingConnectionId(repo),
projectRef,
...this.deps.getLocalGitArgs(repo)
)
}
async updateGitLabRepoMR(
repoSelector: string,
iid: number,
updates: GitLabMRUpdate,
projectRef?: GitLabProjectRef | null
) {
const repo = await this.deps.resolveRepo(repoSelector)
return updateMR(
repo.path,
iid,
updates,
repo.issueSourcePreference,
getRepoRoutingConnectionId(repo),
projectRef,
...this.deps.getLocalGitArgs(repo)
)
}
async updateGitLabRepoMRReviewers(
repoSelector: string,
iid: number,
reviewerIds: number[],
projectRef?: GitLabProjectRef | null
) {
const repo = await this.deps.resolveRepo(repoSelector)
return updateMRReviewers(
repo.path,
iid,
reviewerIds,
repo.issueSourcePreference,
getRepoRoutingConnectionId(repo),
projectRef,
...this.deps.getLocalGitArgs(repo)
)
}
}