Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions quest/m0/agent-test-env.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
# [XS] Keep agent tests independent of the caller's agent
# [XS] Keep tests independent of the caller's environment

## Goal

`just test` passes inside a Claude Code or Codex session. `test/agent.test.ts`
clears the caller's agent variables (`CLAUDE_CONFIG_DIR`, `ANTHROPIC_*`,
`CODEX_*`) before it compares the environment it launches with.
`just test` passes inside a Claude Code or Codex session and under any user
git configuration. `test/agent.test.ts` clears the caller's agent variables
(`CLAUDE_CONFIG_DIR`, `QUEST_WORKTREE_HOME`, `ANTHROPIC_*`, `CODEX_*`) before
it compares the environment it launches with, and every git fixture turns off
commit signing.

## Plan

Decided 2026-10-28 (user, /quest-plan of the follow-ups from a 25-PR /quest-complete run). Almost every merge agent in that run had to unset `CLAUDE_CONFIG_DIR`
before `just test` would pass, because the launched agent's expected
environment picked it up. Decide whether the launch itself should strip any
of these variables or only the test should. Name that choice in the PR.

Decided 2026-10-30 (user, /quest-plan of the follow-ups from
[moq-dev/quest#292](https://github.com/moq-dev/quest/pull/292)):

- Signing:
- ✅ Fold it into this quest
- Drop it
- `test/review.test.ts` commits its fixtures without `commit.gpgsign=false`,
unlike `move`, `snapshot`, `landed`, and `start`. Under a global SSH
signing config it fails with `cannot run ssh-keygen`, since the fixture's
PATH lacks it. Turn signing off there, and check the other fixtures for the
same gap.
9 changes: 8 additions & 1 deletion quest/m0/host-land/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ can hand off instead.
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.
- Security (user, 2026-10-30): the gate runs as a dedicated user under a
hardened unit. PR code and Codex run in the runner's sandbox with no
credentials. moq-bot tokens are minted per push and scoped to this
repository. Codex reaches its model through the host's CLIProxyAPI, so the
Codex login stays outside the sandbox. A maintainer's approval of the
pinned head covers evaluating its devShell, but the fixer may not touch
files that run outside the sandbox.
- 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`).
Expand All @@ -80,6 +87,6 @@ can hand off instead.

## Required

- [moq-bot's App key](/quest/m0/host-land/bot-key.md) - condition: the maintainer places the key on the gate host
- [Gate host setup](/quest/m0/host-land/bot-key.md) - a dedicated user holding moq-bot's key and a CLIProxyAPI client key, unreachable from the sandbox
- [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
38 changes: 30 additions & 8 deletions quest/m0/host-land/bot-key.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
# [XS] moq-bot's App key on the gate host
# [S] The gate host's user, moq-bot key, and Codex access

## 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.
The maintainer's gate host has a dedicated unprivileged system user that runs
the gate under a hardened systemd unit. Only that user can read moq-bot's App
private key, and nothing the gate sandboxes can read it or the maintainer's
Codex login; sandboxed Codex holds only the gate's revocable CLIProxyAPI
client key. Check it by minting a moq-bot token as that user and reading the
repository with it, and by failing to read the bot key from another user and
from inside the sandbox. Placing the bot key and issuing the client key
wait on the maintainer.

## Plan

Expand All @@ -14,6 +18,24 @@ 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.
- Isolation (user, 2026-10-30):
- ✅ A dedicated user plus the sandbox
- A microVM per batch
- A separate machine
- Store the keys as files the gate user alone can read (mode 0400), or as
1Password `op://` references as `QUEST_API_KEYS` does. Never put the bot key
in a sandboxed process's reach.
- Harden the unit: `NoNewPrivileges`, `ProtectSystem=strict`,
`ProtectHome=yes` apart from the gate's own state directory, `PrivateTmp`,
and no other users' files. The gate still needs unprivileged user
namespaces for bubblewrap (see
[AppArmor](/quest/m0/kix/sandbox/apparmor.md) on Ubuntu).
- Codex reaches its model through the host's CLIProxyAPI service
(`cliproxyapi.service`, `127.0.0.1:8317`), which holds the maintainer's
Codex login in systemd credentials and bills the Codex plan's credits
(user, 2026-10-30). Proxy over a key in the sandbox:
- Capped API key, no git creds
- ✅ Key via proxy, through the CLIProxyAPI already running here
- The gate gets its own CLIProxyAPI client key, so it can be revoked
alone. The sandbox can see that client key; it reaches only the proxy, and
the login itself stays outside.
21 changes: 17 additions & 4 deletions quest/m0/host-land/daemon.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,22 @@ Decided 2026-10-30 (user, /quest-plan); see the
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.
on the tip through `quest gate`, in the runner's sandbox
(`src/runner/sandbox.ts`): no home directories, GitHub token, `gh` config,
SSH agent, or keys, and network only to package registries.
- **Trust** (user, 2026-10-30). A maintainer approved the pinned head, so
the gate evaluates its devShell even when it has non-maintainer commits.
The pin is what makes that safe, so never check a head other than the
pinned one.
- Base devShell, untrusted flake changes handed back
- Refuse untrusted heads until the VM ships
- Wait for the VM
- ✅ Fine as is: a maintainer already approved the merge
- **Credentials.** Run as the gate's own user
([setup](/quest/m0/host-land/bot-key.md)). Mint a moq-bot installation
token for each push, scoped to this repository with contents and
pull-request write, adding workflows write only when the batch touches
`.github/workflows`.
- **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
Expand All @@ -39,4 +52,4 @@ Decided 2026-10-30 (user, /quest-plan); see the

## Required

- [moq-bot's App key](/quest/m0/host-land/bot-key.md) - the host can push as moq-bot
- [Gate host setup](/quest/m0/host-land/bot-key.md) - the gate's user can push as moq-bot and reach Codex
12 changes: 10 additions & 2 deletions quest/m0/host-land/fixer.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,16 @@ Decided 2026-10-30 (user, /quest-plan); see 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.
- **Credentials** (user, 2026-10-30). Codex runs in the gate's sandbox with
no git or GitHub credential and network only to the host's CLIProxyAPI,
through the gate's client key ([setup](/quest/m0/host-land/bot-key.md)).
It commits locally. The daemon checks the commits against the rules here,
pushes them as moq-bot, and requeues.
- **Off-limits paths.** A maintainer's approval covers the pinned head, so
the gate may evaluate that head's devShell. The fixer's commits come after
the approval, so they may not touch what runs outside the sandbox:
`flake.nix`, `flake.lock`, `nix/`, `.envrc`, or `.github/`. A fix that
needs them is handed back, and the daemon refuses to push one that does.

## Required

Expand Down
Loading