Skip to content

Extend run.replicas to GitLab, Forgejo and Azure DevOps triggers #187

Description

@edgehero

What

run.replicas races N independent containers on one forge event, each on its own branch, each opening its own review request — and it works on GitHub triggers only. The refusal message already admits the truth (worker/src/triggers.mjs:541-543): "not yet covered for gitlab/forgejo/azure triggers (github only in this version); every forge mints its branch the same way, so this is a gap to close, not a limit." Close it: replicas on label, comment and pull_request triggers for GitLab, Forgejo and Azure DevOps.

What already works (don't redo this)

Almost everything below the surface is forge-generic today:

  • The enqueue primitives. enqueueForgeJob (worker/src/queue.mjs:118) takes replica/replicas for any kind; forgeDeliveryJobId (worker/src/job-id.mjs:86-99) suffixes -r<i> onto any forge's prefix; the semantic dedup key (queue.mjs:159) already composes the per-forge separator (#/!, forges.mjs:145-148) with the :r<i> suffix — a replicated GitLab job would dedup as project!5:flow:r2 with no new code.
  • The receiver config layer. loadTriggers writes replicas onto every forge's rules already (receiver/src/config.mjs:278-284) — it is just always undefined because parseTriggers refuses first.
  • The branch minter. issueBranch(number, replica) (worker/src/branch.mjs:76-79) has no forge concept; the pi/issue-* safety floor in guardrails/HARD_RULES.md:35-41 is a glob, not a GitHub noun.
  • The shared preparer. prepare-github.mjs:195-201 already delivers replica/replicas to all four prompt builders — the gitlab/forgejo/azure builders destructure them away today.
  • The per-forge "open the review request" logic. Each single-run path already pushes a branch and opens an MR/PR its own way (gitlab-prompt.mjs:77-90, forgejo-prompt.mjs:77-87, azure-prompt.mjs:71-82); replicas just multiply what exists.
  • The record, the image gate, the budget. replica/replicas on the run record are forge-neutral (interfaces.md:1780-1781); the replicas image-capability preflight is forge-agnostic (worker/src/image-preflight.mjs:87-89); N replicas make N honest budget reservations by construction (requirements.md:1232-1236).

The actual gap, enumerated

  1. The schema refusalworker/src/triggers.mjs:541-543 throws on any non-github kind. Delete the arm; keep the cron/local refusal above it untouched (that one is a hazard, not coverage — see below).
  2. The fanout loop — only the two GitHub producers loop (receiver/src/receiver.mjs:200-204, receiver/src/poller.mjs:783-788). The gitlab (receiver.mjs:247-262), forgejo (:298-310) and azure (:355-370) arms enqueue once and never read result.job.replicas. Each needs the same 5-line loop, with the same partial-failure idempotence the GitHub arm documents (receiver.mjs:196-199): 503 on failure at replica k, retry converges because queued ids dedup.
  3. The per-forge filtersfilter-gitlab.mjs, filter-forgejo.mjs, filter-azure.mjs carry no replicas field (zero grep hits): one job-literal line plus three rule-read lines each, mirroring filter.mjs:123-127/165/211/305.
  4. The prompt builders — the three non-GitHub builders call issueBranch with one argument (gitlab-prompt.mjs:71, forgejo-prompt.mjs:72, azure-prompt.mjs:65) and have no replica paragraph. Pass the index through and add the divergence lines analogous to github-prompt.mjs:98-110/116-127, including the [r<i>/<n>] title marker (agent-honored, not host-enforced — the existing posture, requirements.md:1246-1248).

Constraints to preserve (each is load-bearing today)

  • The resume × replicas mutual refusal holds per forge (triggers.mjs:547-549, branch.mjs:18-21, session-key.mjs:81-87): a resumed run continues one lineage; replicas exist to fork it.
  • REPLICAS_MAX = 3 stays derived from PI_CONCURRENCY's default (triggers.mjs:95-101); this issue does not touch the ceiling.
  • Chain fanout stays bounded: outbox.mjs:51 returns early for every non-local kind, so no new bound is needed.
  • The dedup rule that made GitHub replicas safe (interfaces.md:1543-1545: a duplicate queue.add under a taken id is silently ignored, hence the distinct semantic keys) must be asserted per forge in tests.
  • OQ-017 (the PR-head-branch sharing hazard) is written in GitHub nouns but applies verbatim to MR targets — widening coverage widens its blast radius without changing its argument; the OQ gets a scope note, not a redesign.
  • Non-GitHub forges have webhook arms only (the poller is GitHub-only by construction, poller.mjs:105), so replicas there are webhook-only; say so rather than implying parity.

Explicitly NOT proposed

  • Replicas on cron/local. That refusal is a hazard, not coverage: a local job's /workspace IS the operator's folder, and two replicas would edit one working tree with no gate and no undo (triggers.mjs:538-540, design.md:1859-1861, constitution.md:674). It stays, message and test intact.
  • Touching the budget: N reservations is the honest count, and softening caps for replicas would turn a cost multiplier into a cap bypass (requirements.md:1232-1236).
  • A panel key or model-callable path for the field (interfaces.md:1551-1554).

Acceptance

run.replicas: 2 on a gitlab, forgejo or azure label/comment/pull_request trigger loads in both services; one delivery mints N jobs with distinct <prefix><id>-r<i> jobIds and distinct :r<i> semantic keys; each replica's prompt names its own pi/issue-<n>-r<i> branch and the [r<i>/<n>] title marker; the run records carry replica/replicas; the image capability gate refuses per replica set exactly as on GitHub; partial enqueue failure 503s and converges on retry; cron/local and resume combinations keep their existing refusals verbatim; REQ-REPLICA-RUNS' scope clause, INT-TRIGGERS-FILE-CONTRACT's schema note and acceptance rows, DES-REPLICA-INDEX-REACHES-THE-BRANCH, docs/replicas.md:107 and the four forge docs are amended in the same PRs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions