From 5c43f01ea9b82abf4dfbc777fc9b932abe421883 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 30 Oct 2026 16:10:38 +0000 Subject: [PATCH] quest: plan merging through a host gate (#291) * quest: plan merging through a host gate Inline merge picks, small batches landed from the maintainer's host, and an asynchronous Codex fixer for ejected batches instead of bisection. Co-authored-by: Claude Opus 5.5 --- quest/m0/README.md | 1 + quest/m0/helpers/land-adopt.md | 8 +++ quest/m0/host-land/README.md | 86 ++++++++++++++++++++++++++++++ quest/m0/host-land/bot-key.md | 19 +++++++ quest/m0/host-land/daemon.md | 42 +++++++++++++++ quest/m0/host-land/fixer.md | 36 +++++++++++++ quest/m0/host-land/inline-merge.md | 30 +++++++++++ 7 files changed, 222 insertions(+) create mode 100644 quest/m0/host-land/README.md create mode 100644 quest/m0/host-land/bot-key.md create mode 100644 quest/m0/host-land/daemon.md create mode 100644 quest/m0/host-land/fixer.md create mode 100644 quest/m0/host-land/inline-merge.md diff --git a/quest/m0/README.md b/quest/m0/README.md index f79321ce..bf94f38b 100644 --- a/quest/m0/README.md +++ b/quest/m0/README.md @@ -39,6 +39,7 @@ ownership-manifest design: ## Required +- [Merge through a host gate](/quest/m0/host-land/README.md) - inline merge picks, small batches landed from the maintainer's host, and a Codex fixer instead of bisection - [Codex cloud off](/quest/m0/codex-cloud-off.md) - automatic Codex cloud reviews stop once no open PR relies on them - [Review reports an expired login](/quest/m0/review-login.md) - `quest review` says to log in again instead of failing with a 401 - [Live sandbox test](/quest/m0/review-sandbox.md) - the real reviewer CLIs run nothing a reviewed head plants diff --git a/quest/m0/helpers/land-adopt.md b/quest/m0/helpers/land-adopt.md index a5f130a3..cdce8ebf 100644 --- a/quest/m0/helpers/land-adopt.md +++ b/quest/m0/helpers/land-adopt.md @@ -36,3 +36,11 @@ What remains is the fork path. quest is done only once a fork PR that allows maintainer edits lands through the gate and shows as merged; any fix it needs lands here. GitHub offers maintainer edits only on forks a user owns, not an organization. +- **Host gate (user, 2026-10-30).** The gate moves to the maintainer's host + ([host daemon](/quest/m0/host-land/daemon.md)), which pushes with moq-bot's + App key instead of the `token.moq.dev` exchange above, and land.yml goes + away. Verify the fork path on the host gate. + +## Required + +- [Host daemon](/quest/m0/host-land/daemon.md) - the gate the fork path is verified on diff --git a/quest/m0/host-land/README.md b/quest/m0/host-land/README.md new file mode 100644 index 00000000..5ce0d351 --- /dev/null +++ b/quest/m0/host-land/README.md @@ -0,0 +1,86 @@ +# Merge through a host gate + +## Goal + +Picking a PR to merge starts no long-lived agent. `/quest-merge` queues it +inline only when a counted review names its head, or the head is trivial; +otherwise `/quest-iterate` runs first. A gate on the maintainer's host lands +small batches: a base PR plus up to floor(log₂ n) others that merge cleanly, +checked once on the chain's tip and pushed as one squash commit per PR. A +conflict on the base PR or a failing batch ejects the PRs and hands them to an +asynchronous Codex fixer, which makes only mechanical fixes and requeues the +healthy PRs. Bisection goes away. + +Non-goals: other repositories adopting the gate (moq-dev/moq, moq-dev/moq.pro +plan their own; moq.pro needs only `allow_auto_merge` and the ruleset, since +moq-dev is on GitHub Team), and changing the review policy beyond the +mechanical-fix exemption. + +## Plan + +Decided 2026-10-30 (user, /quest-plan). Per-PR `/quest-merge` sub-agents were +wasteful: each one stayed alive to fix conflicts and failures that the gate +can hand off instead. + +- Goal, as stated above: + - ✅ Yes, as stated + - Also include moq adoption + - Narrower: only the skill changes +- Where the gate runs. Keeping it in GitHub Actions was recommended, since it + is live and isolates the push credential; the user chose the host. + - Keep GitHub Actions + - ✅ Move to this host +- Architecture, once on the host: + - Host orchestrates, CI on Actions + - ✅ Everything on this host: the worker runs the check locally through + `quest gate`, pushes, and starts Codex. No Actions involved in landing. + - Everything stays in Actions +- Queue signal. The user asked for a check nobody reports, so PRs stay in + auto-merge until the host reaches them; `main`'s ruleset already requires + the unreported `quest land` check. GitHub keeps auto-merge across a + writer's push, so the gate compares the head to the pin and ejects + `head_moved`. + - ✅ Pinned auto-merge (`quest merge --land`) plus `quest:merge` + - Label only, with the label's time against the last push +- Retry cap for the fixer: + - 2 attempts + - ✅ 1 attempt + - The review-round checkpoint +- Push credential: + - Your gh auth through the admin bypass, check sandboxed + - ✅ A moq-bot App key on this host, check sandboxed + - Your gh auth, no sandbox +- Runtime. A one-pass systemd timer was recommended: + - systemd timer, one pass per run + - ✅ A long-running daemon +- Review of the fixer's changes: + - Claude reviews them through `quest review` + - ✅ None: Codex makes only mechanical fixes; anything that would need a + review is ejected and handed back +- Mechanical fixes: + - ✅ No behavior change, recorded in `## Review` + - Tool-verifiable only +- Batch: + - ✅ The longest-queued PR as the base, plus the next PRs in queue order + that merge cleanly onto the chain, up to floor(log₂ n) + - Quest priority order +- Open PRs: land + [moq-dev/quest#289](https://github.com/moq-dev/quest/pull/289) now, as it + fixes live wrong ejections until the host gate replaces that code; close + [moq-dev/quest#277](https://github.com/moq-dev/quest/pull/277), since an + ejection now removes `quest:merge` and the fixer adds it back. +- This epic ranks first in m0. It replaces the hook-started kix fixer that + [moq-dev/quest#276](https://github.com/moq-dev/quest/pull/276) proposes + (`kix/eject-fix`). +- The epic finishes with an end-to-end run on moq-dev/quest: a batch with one + PR that fails a check, one that conflicts with the base, and one healthy + PR. The healthy PR lands after the requeue, the mechanical one lands after + its fix, and the other is handed back. `CONTRIBUTING.md` and the README's + landing section describe the host gate. + +## Required + +- [Inline merge](/quest/m0/host-land/inline-merge.md) - `/quest-merge` queues inline or recommends `/quest-iterate`; no per-PR merge sub-agents +- [moq-bot's App key](/quest/m0/host-land/bot-key.md) - condition: the maintainer places the key on the gate host +- [Host daemon](/quest/m0/host-land/daemon.md) - `quest land serve` lands batches and ejects without bisecting +- [Codex fixer](/quest/m0/host-land/fixer.md) - ejected batches get one mechanical fix attempt and healthy PRs are requeued diff --git a/quest/m0/host-land/bot-key.md b/quest/m0/host-land/bot-key.md new file mode 100644 index 00000000..a53dba9e --- /dev/null +++ b/quest/m0/host-land/bot-key.md @@ -0,0 +1,19 @@ +# [XS] moq-bot's App key on the gate host + +## Goal + +The maintainer's gate host can mint moq-bot installation tokens for +moq-dev/quest from moq-bot's App private key, kept readable only by the user +that runs the gate. Check it by minting a token there and reading the +repository with it. This waits on the maintainer placing the key. + +## Plan + +Decided 2026-10-30 (user, /quest-plan): the host gate pushes as moq-bot, +which `main`'s ruleset lets bypass the `quest land` check. Today moq-bot's +token comes only from `token.moq.dev` through land.yml's GitHub ID token, which +a host cannot present. + +- Accept a key file path or a 1Password `op://` reference, as + `QUEST_API_KEYS` does. +- Never expose the key or a token to the sandboxed check. diff --git a/quest/m0/host-land/daemon.md b/quest/m0/host-land/daemon.md new file mode 100644 index 00000000..d2d85962 --- /dev/null +++ b/quest/m0/host-land/daemon.md @@ -0,0 +1,42 @@ +# [L] Land batches from a host daemon + +## Goal + +`quest land serve` runs on the maintainer's host as a long-running daemon and +replaces the Land workflow. It reads the queue (PRs auto-merging with a +`quest merge --land` pin), builds a batch, checks the chain's tip once, and +fast-forwards `main` as moq-bot when it passes, one squash commit per PR. +When the base PR conflicts with `main` or the batch fails, it ejects the batch +and starts the fixer. It never bisects. + +## Plan + +Decided 2026-10-30 (user, /quest-plan); see the +[epic](/quest/m0/host-land/README.md). + +- **Batch.** The base is the longest-queued PR. Add the next PRs in queue + order that merge cleanly onto the chain with the quest merge driver, up to + floor(log₂ n) for n queued PRs. A PR that conflicts with the chain is + skipped and stays queued. Only the base's own conflict ejects it. +- **Check.** Run the repository's check (`just check` and `just test` here) + on the tip through `quest gate`, in a sandbox holding no GitHub token, + `gh` config, SSH agent, or the bot key. Reuse the runner's sandbox where it + fits. +- **Eject.** On a failing tip, eject every PR in the batch: disable + auto-merge, remove `quest:merge`, and comment with the reason, keeping the + hidden marker `quest status` reads. Keep the resolve-time ejections + (`head_moved`, `not_pinned`, review and check states). +- **Delete** land.yml, `quest land check`'s bisection and `--budget`, and the + three-phase split that existed to keep the credential away from CI. Drop + the ID-token guard in `test/workflows.test.ts`. `token.moq.dev`'s land + endpoint lives in moq-dev/moq; suggest its removal there as a follow-up. +- **Daemon.** One process per repository checkout. It reacts to the queue by + reading GitHub's state cheaply between batches, holds a lock so two never + run, and resumes cleanly after a restart. Keep its code repository-agnostic + so other repositories can adopt it. +- Test the batch selection, a base conflict, a skipped conflicting PR, a + failing tip ejecting the whole batch, a moved head, and a restart mid-batch. + +## Required + +- [moq-bot's App key](/quest/m0/host-land/bot-key.md) - the host can push as moq-bot diff --git a/quest/m0/host-land/fixer.md b/quest/m0/host-land/fixer.md new file mode 100644 index 00000000..17b8e7d3 --- /dev/null +++ b/quest/m0/host-land/fixer.md @@ -0,0 +1,36 @@ +# [M] Fix ejected batches with Codex + +## Goal + +When the host gate ejects a batch, it starts `codex exec` on the batch in the +background and keeps landing other work. Codex reads the failure, requeues +every PR it finds healthy unchanged, makes mechanical fixes to the rest and +requeues them, and hands back anything else. Each ejection gets one attempt. + +## Plan + +Decided 2026-10-30 (user, /quest-plan); see the +[epic](/quest/m0/host-land/README.md). + +- **Mechanical only.** A fix may not change behavior: resolving a conflict + while keeping both sides' intent, formatting and lint fixes, regenerating + lockfiles or generated files, following a rename the base made, or + requeueing unchanged after a flaky failure. Codex adds a line to the PR's + `## Review` record, `mechanical fix by Codex: `, naming the new + head, and requeues with `quest merge --land`. +- **Guide.** Add that exemption to the guide's Reviews section beside the + conflict-free base merge and the reviewer's patch. Anything that would + need a review is not mechanical. +- **Hand back.** Anything else, or a PR ejected again after its one attempt, + stays unqueued with a comment giving Codex's diagnosis and a + recommendation. `/quest-complete` asks about it again, recommending + `/quest-iterate`. Count the attempt from the ejection comments since the + maintainer's last pick. +- A flaky failure the fixer requeues becomes a suggested follow-up in its + comment. +- Codex runs with the maintainer's own Codex login and a write credential + for pushing to the PR branch and requeueing, never the bot key. + +## Required + +- [Host daemon](/quest/m0/host-land/daemon.md) - ejects batches and starts the fixer diff --git a/quest/m0/host-land/inline-merge.md b/quest/m0/host-land/inline-merge.md new file mode 100644 index 00000000..7ad6acea --- /dev/null +++ b/quest/m0/host-land/inline-merge.md @@ -0,0 +1,30 @@ +# [S] Merge inline from the coordinator + +## Goal + +`/quest-complete` and `/quest-spawn` merge a picked PR inline, without a +sub-agent. `/quest-merge` reads the PR with `quest status`, and when a +`## Review` record the guide counts names the head (a recorded skip for a +trivial change included), it queues that head with `quest merge --land` and +reports. Otherwise it recommends `/quest-iterate`, which runs the review and +any changes, and queues afterwards. `/quest-spawn` and `/quest-complete` +recommend `/quest-iterate` for every PR whose head has no counted review. + +## Plan + +Decided 2026-10-30 (user, /quest-plan); see the +[epic](/quest/m0/host-land/README.md). + +- `/quest-merge` no longer fixes conflicts, failing checks, or review + findings, and no longer runs `quest check --base` or waits: the gate's + check covers `quest check`, and its fixer owns conflicts and failures. + Posted review findings that need changes make it recommend + `/quest-iterate` instead of queueing. +- Drop the skills' re-dispatch of `/quest-merge` after a `merge_conflict` or + `failed_checks` ejection; the fixer requeues those or hands them back, + and a handed-back PR is asked about again. +- This works on today's land.yml gate too, so it can land before the host + gate. +- Reconcile with [moq-dev/quest#288](https://github.com/moq-dev/quest/pull/288) + and [moq-dev/quest#276](https://github.com/moq-dev/quest/pull/276), which + also rewrite these skills, whichever lands first.