Skip to content

A disposable fixture repo, `petrova-codes/wip-cap-fixture`, for IR-007's real WIP-cap E2E test — cross-runner locking scoped out to a follow-up

Date: 2026-09-13 Status: closed Supersedes: none Superseded-by: none — current

irina/tickets/IR-007-wip-cap-correctness-test.xml asks for proof the fleet-wide WIP cap (host/src/tools/wip-cap.ts, IR-001) refuses a real 4th concurrent PETROVA-authored PR, not just a unit-level count check. The existing suite (host/tests/wip-cap.test.ts, host/tests/wip-cap-wiring.test.ts) mocks ActsSource entirely — it proves the count/threshold logic and the process-local serialization withWipCapGuard adds, but never touches real GitHub state.

Two problems surfaced while scoping this:

  1. No safe target repo exists. ActsSource.recent() (host/src/sources/acts.ts:88) always resolves its target list via PetrovaSource.loadRegistry() — i.e. registry.yaml. Every registered repo is either fleets_allowed: [] by design (no agent automation permitted at all — using one to open/close throwaway test PRs would contradict that lock) or, for kahn-hq, has a second human and CODEOWNERS (real noise a churn test shouldn’t produce there).
  2. withWipCapGuard’s lock is process-local. It serializes check-then-act within one Node process (host/src/tools/wip-cap.ts’s own header comment says this explicitly). IR-007’s C1 — two separate irina-cycle.yml runs (two separate GitHub Actions runners, i.e. two separate processes) not both slipping through — is not covered by today’s design at all. No amount of E2E test-writing proves a claim the code doesn’t make; building a real cross-process/cross-runner lock is its own piece of work, not a test.

Fixture repo, not a registry entry. Created petrova-codes/wip-cap-fixture — a new, empty, private, disposable GitHub repo under the same org, covered automatically by the existing petrova-act App installation (repository_selection: all). It is never added to registry.yaml. Instead, the E2E test constructs its own PetrovaSource pointed at a small fixture registry file (host/tests-e2e/fixtures/registry.yaml, one entry, this repo’s URL) and wires that into ActsSource directly — using the same production code path (PetrovaSource → ActsSource → wipCapGuard), against real GitHub API responses, without ever touching the real fleet’s governance source-of-truth. This keeps registry.yaml’s PR-only, human-reviewed nature (see CLAUDE.md’s “Registry edits are PR-only”) untouched by a throwaway test fixture.

C1 (cross-runner race) is scoped OUT of this ticket, not answered by a weaker test. withWipCapGuard’s process-local lock genuinely cannot pass a real two-runner concurrent test — there’s nothing to test that would pass honestly. Rather than write a same-process test and call it C1 (which the existing mocked suite already does, more cheaply), this act files IR-012 for a real cross-process/cross-runner lock (candidate shapes: a committed lease file pushed with optimistic concurrency, or a GitHub Deployments-API-based lock) as its own ticket. IR-007 closes on C2/C3 (real 4th-PR refusal, real sequential success) plus the existing mocked concurrency suite for same-process C1; the cross-runner gap is tracked, not silently dropped.

Manual workflow_dispatch only. The E2E test opens and closes real PRs against the fixture repo — real GitHub API writes with real (if small) cost and churn. It runs only on-demand (.github/workflows/wip-cap-e2e.yml, workflow_dispatch trigger, no schedule or pull_request trigger), never as part of normal CI on this repo’s own PRs.

  • Register the fixture repo in registry.yaml — rejected: would give a throwaway test fixture the same governance weight as a real consumer repo (PR-only edits, decision-doc citations, fleets_allowed review), for a repo that exists solely to be spammed with disposable branches. The PetrovaSource constructor already accepts an arbitrary registry path — using that seam is a two-line test fixture instead of a registry PR.
  • Reuse kahn-hq — rejected: real second human, real CODEOWNERS, real production repo. Opening/closing 4 PRs there per test run is noise in someone else’s inbox, not just this repo’s problem.
  • Build the cross-process lock now, inside IR-007 — rejected (for now): materially larger scope (a new distributed-locking primitive, its own failure modes to test) than “write the E2E test the ticket already describes.” Deferred to IR-012 rather than silently narrowing IR-007’s claim without a paper trail.

For code: host/tests-e2e/wip-cap-e2e.test.ts (new, not run by default npm test — a separate workflow_dispatch-only CI job invokes it) plus a one-entry fixture registry file. No change to host/src/tools/wip-cap.ts or registry.yaml.

For docs: this doc; irina/tickets/IR-007-wip-cap-correctness-test.xml gets an inline note pointing here; new ticket IR-012 files the cross-runner lock as separate work.

For in-flight phases: none.

For invariants: none changed. This is infrastructure for a test, not a policy change — the WIP cap’s ratified value (3) and mechanism are untouched.

  • irina/tickets/IR-007-wip-cap-correctness-test.xml (the ticket this act closes C2/C3 of)
  • irina/tickets/IR-001-wip-cap-enforcement.xml (the cap this test verifies)
  • host/src/tools/wip-cap.ts (implementation and its own honest comment about the process-local lock’s scope)
  • host/src/sources/acts.ts:88 (ActsSource.recent()’s registry-derived target list — the seam this act uses without touching the real registry)
  • CLAUDE.md (“Registry edits are PR-only”)
  • Subagent: claude-sonnet-5 (session_01488gmoyur1UAMfpNMgUAhC)
  • Human: alex@devarno.com — ratifies creating petrova-codes/wip-cap-fixture as a disposable, non-registered fixture repo, scoping IR-007’s C1 out to a new IR-012 ticket, and running the resulting E2E test only via manual workflow_dispatch.

Countersigned by human:alex@devarno.com on 2026-09-13 — commit 9eae4c3c (this file’s creation commit) is authored by the human directly, per docs/decisions/2026-09-05-proxy-countersign-retired.md’s post-retirement definition. Line added retroactively by the agent as scribe.