fix(ci): make runner image tests deterministic (#12452)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Pull request checks protect the runtime and authorization boundaries
> - The same checks must produce the same result on GitHub and RunsOn
Ubuntu images
> - One runtime test assumed that a shell PID always owns the listening
socket
> - One watchdog test denied issue reads while it tried to test
assignment denial
> - These assumptions caused image-sensitive failures during the AWS
runner canary
> - This pull request tests the production contracts directly
> - The benefit is a reliable CI result across both runner images

## Linked Issues or Issue Description

Refs #12350

## What Changed

- Verify stale service ownership through the existing process-group
ownership helper.
- Allow normal issue reads in the watchdog reassignment fixture.
- Assert that the watchdog reassignment reaches and denies the
`tasks:assign` guard.

## Verification

- `pnpm --filter @paperclipai/server typecheck`
- `pnpm exec vitest run server/src/__tests__/workspace-runtime.test.ts
-t "does not reuse a stopped auto-port service port while another
process owns it"`
- `pnpm exec vitest run
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts -t
"still enforces normal assignment guards for watchdog reassignment"`
- Ran the complete issue agent mutation ownership suite six times. All
522 test executions passed.
- Ran the runtime regression case eight times. All eight test executions
passed.

## Risks

- Low risk. This pull request changes test fixtures and assertions only.
- The process-group assertion matches the ownership rule that the
runtime already uses.
- The watchdog fixture still denies `issue:mutate` and `tasks:assign`.

## Model Used

- OpenAI Codex with GPT-5.6 (`gpt-5.6-sol`). The model used reasoning,
tool use, and code execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with Fixes, Closes, or
Refs OR (b) described the issue in-PR following the relevant issue
template
- [x] I have not referenced internal or instance-local Paperclip issues
or links
- [x] My branch name describes the change and contains no internal
Paperclip ticket id
- [x] I have run relevant tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes. No
documentation change is required for this test-only fix.
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
This commit is contained in:
Dotta 2026-08-28 13:01:59 -05:00 committed by GitHub
parent b2752b21d5
commit 8da49d6ea7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 14 additions and 3 deletions

View File

@ -2369,9 +2369,12 @@ describe("agent issue mutation checkout ownership", () => {
// Base boundary denied AND tasks:assign denied: the watchdog grant lets the
// mutation past the ownership boundary, but the assignment guard must still bite.
mockAccessService.decide.mockImplementation(async (input: { action: string }) => ({
allowed: input.action === "company_scope:read",
allowed: input.action === "company_scope:read" || input.action === "issue:read",
action: input.action,
reason: input.action === "company_scope:read" ? "allow_explicit_grant" : "deny_policy_restricted",
reason:
input.action === "company_scope:read" || input.action === "issue:read"
? "allow_explicit_grant"
: "deny_policy_restricted",
explanation:
input.action === "tasks:assign"
? "Target agent requires approval before task assignment."
@ -2385,6 +2388,9 @@ describe("agent issue mutation checkout ownership", () => {
expect(res.status, JSON.stringify(res.body)).toBe(403);
expect(res.body.error).toContain("requires approval");
expect(mockAccessService.decide).toHaveBeenCalledWith(
expect.objectContaining({ action: "tasks:assign" }),
);
expect(mockIssueService.update).not.toHaveBeenCalled();
});

View File

@ -8238,7 +8238,12 @@ describeEmbeddedPostgres("workspace runtime startup reconciliation", () => {
expect(services[0]?.url).not.toBe(rootUrl);
await expect(fetch(services[0]!.url!)).resolves.toMatchObject({ ok: true });
await expect(fetch(healthUrl)).resolves.toMatchObject({ ok: false, status: 503 });
expect(await readLocalServicePortOwner(stalePort!)).toBe(staleProcess.pid);
const stalePortOwnerPid = await readLocalServicePortOwner(stalePort!);
expect(stalePortOwnerPid).not.toBeNull();
expect(staleProcess.pid).toBeTypeOf("number");
await expect(
isLocalServiceProcessOwnedBy(stalePortOwnerPid!, staleProcess.pid!),
).resolves.toBe(true);
} finally {
leasedRunIds.delete(runId);
await releaseRuntimeServicesForRun(runId);