feat(emdash-bot): maintainer-triggered investigation + fix-loop states (bot next-gen 2/5) - #2376
Conversation
|
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-cache | 0440c80 | Aug 09 2026, 11:25 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-playground | 0440c80 | Aug 09 2026, 11:25 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-do | 0440c80 | Aug 09 2026, 11:24 AM |
Scope checkThis PR changes 2,070 lines across 16 files. Large PRs are harder to review and more likely to be closed without review. If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs. See CONTRIBUTING.md for contribution guidelines. |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/auth-atproto
@emdash-cms/blocks
@emdash-cms/cloudflare
@emdash-cms/contentful-to-portable-text
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/plugin-cli
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/registry-verification
@emdash-cms/sandbox-workerd
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-field-kit
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
There was a problem hiding this comment.
Pull request overview
Extends the infra/emdash-bot “next-gen” state machine to support maintainer-triggered investigation and a fix loop (preview → reporter confirm/reject → draft PR or branch reap), and makes the previously-promised generated artifact contract (machine.json + BOT_STATE_MACHINE.md) real with a generator and drift tests.
Changes:
- Adds new bot machine states/events/actions for investigation + fix-loop, with corresponding router/orchestrator wiring and GitHub side-effects (draft PR + branch deletion).
- Introduces deterministic artifact renderers + a
bot:generatescript, commits the generated artifacts, and adds unit drift tests to prevent future drift. - Adds unit/integration test coverage for authorization gating and the new fix-loop paths (confirm/reject/expire/skip).
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| infra/emdash-bot/tsconfig.json | Enables TS extension imports needed by the artifact-renderer module graph. |
| infra/emdash-bot/tests/unit/router.test.ts | Adds router-level assertions covering new investigation + fix-loop transitions and command offerings. |
| infra/emdash-bot/tests/unit/machine-artifacts.test.ts | Adds byte-for-byte drift tests for committed machine artifacts. |
| infra/emdash-bot/tests/integration/orchestrator.test.ts | Adds orchestrator integration coverage for maintainer gating, fix loop happy path, rejection/expiry reaping, and skipped fixes. |
| infra/emdash-bot/scripts/machine-artifacts.ts | Adds deterministic renderers for machine.json and BOT_STATE_MACHINE.md. |
| infra/emdash-bot/scripts/generate-machine.ts | Adds a generator script to write the rendered artifacts to disk. |
| infra/emdash-bot/package.json | Adds bot:generate script hook for artifact regeneration. |
| infra/emdash-bot/BOT_STATE_MACHINE.md | Commits generated state machine documentation. |
| infra/emdash-bot/.flue/lib/router.ts | Extends investigation modes and makes outcomeFromResult mode-aware for diagnose/fix. |
| infra/emdash-bot/.flue/lib/orchestrator.ts | Implements fix-loop expiry window + new side effects (open draft PR, reap branch). |
| infra/emdash-bot/.flue/lib/machine.ts | Adds new states/events/actions/transitions for investigation + fix loop. |
| infra/emdash-bot/.flue/lib/machine.json | Commits generated runtime machine snapshot. |
| infra/emdash-bot/.flue/lib/github.ts | Adds deleteBranch() helper and supports draft PR creation. |
| infra/emdash-bot/.flue/lib/comments.ts | Adds user-facing bot comment strings for new states and fix-loop messaging. |
| infra/emdash-bot/.flue/agents/investigate.ts | Adds diagnose/fix modes and adjusts prompting to enforce “diagnose-only” behavior. |
| .oxfmtrc.json | Excludes generated bot artifacts from formatting. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| "build": "vite build", | ||
| "deploy": "vite build && wrangler deploy", | ||
| "cf-typegen": "wrangler types", | ||
| "bot:generate": "node scripts/generate-machine.ts", |
| const botRoot = fileURLToPath(new URL("..", import.meta.url)); | ||
|
|
||
| writeFileSync(`${botRoot}.flue/lib/machine.json`, renderMachineJson()); | ||
| writeFileSync(`${botRoot}BOT_STATE_MACHINE.md`, renderMachineDoc()); |
Overlapping PRsThis PR modifies files that are also changed by other open PRs:
This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
There was a problem hiding this comment.
This is a clean, additive slice of the bot next-gen work. It extends the state machine with maintainer-triggered investigation and a gated fix loop, wires the new actions into the Orchestrator DO and GitHub helpers, and finally makes the generated machine.json + BOT_STATE_MACHINE.md contract real with deterministic renderers and a byte-for-byte drift test. The approach fits the existing architecture: payload-derived actor authorization is preserved, the classifier only selects from state-appropriate non-destructive commands, and the expensive maintainer-only actions are properly gated in EVENTS.
I read the full diff and traced the new states/events through machine.ts, router.ts, orchestrator.ts, comments.ts, the investigate agent, and the GitHub helpers. The transition table is internally consistent (17 states / 30 events / 88 transitions, no duplicates, every non-terminal state has an outgoing edge, every terminal has a reopen edge, all states reachable). Tests cover the new authority rejection, investigation verdicts, fix-loop happy path, reject/expire branch reap, and a regression for the skipped-fix transition.
The only real gap is that the structural validateMachine() validator is exported and described as part of the CI contract, but it is never called by the generator or the drift tests. Because the drift test only checks that the committed artifacts match the rendered output, a malformed machine could still produce matching artifacts and pass CI while violating reachability/terminal-reopen guarantees. Enforcing it is a straightforward follow-up.
|
|
||
| const botRoot = fileURLToPath(new URL("..", import.meta.url)); | ||
|
|
||
| writeFileSync(`${botRoot}.flue/lib/machine.json`, renderMachineJson()); |
There was a problem hiding this comment.
[needs fixing] validateMachine() is exported from machine.ts and the header there says it is "run by the generator and in CI so a malformed edit to this file fails fast", but scripts/generate-machine.ts never calls it. The byte-for-byte drift test only verifies that the committed artifacts match the rendered output; it cannot catch structural problems such as unreachable states, missing reopen edges, or duplicate (state, event) transitions. Wire the validator into the generator so malformed machines fail before the artifacts are written, and add an assertion in tests/unit/machine-artifacts.test.ts.
| writeFileSync(`${botRoot}.flue/lib/machine.json`, renderMachineJson()); | |
| import { renderMachineDoc, renderMachineJson } from "./machine-artifacts.ts"; | |
| import { validateMachine } from "../.flue/lib/machine.ts"; | |
| const botRoot = fileURLToPath(new URL("..", import.meta.url)); | |
| const problems = validateMachine(); | |
| if (problems.length > 0) { | |
| throw new Error(`Invalid machine.ts: ${problems.map((p) => p.message).join("; ")}`); | |
| } | |
| writeFileSync(`${botRoot}.flue/lib/machine.json`, renderMachineJson()); | |
| writeFileSync(`${botRoot}BOT_STATE_MACHINE.md`, renderMachineDoc()); |
Also add to tests/unit/machine-artifacts.test.ts:
import { validateMachine } from "../../.flue/lib/machine.ts";
test("machine.ts passes structural validation", () => {
expect(validateMachine()).toEqual([]);
});628fd7a to
86cec30
Compare
86cec30 to
8913fa0
Compare
There was a problem hiding this comment.
Re-review of the next-gen bot slice. The approach remains sound: the investigation and fix-loop states are additive, authority stays payload-derived (investigate/fix are maintainer-only), the generated-artifact contract is enforced by the generator plus byte-for-byte drift tests, and the router/orchestrator split keeps side effects out of pure state logic.
My previous findings are mostly resolved. The stale PR #1606 / investigate-run.yml references are gone, the event()/ runOpenPr JSDoc no longer narrates obsolete skeleton phases, and tsconfig.json now includes scripts/**/*.ts so the generator and renderers are part of the static program.
The one outstanding item from the previous review is the generator's Node version contract. The script now uses node --experimental-strip-types, which is the right flag, but the workspace still declares "node": ">=22" and this package has no local engines field. --experimental-strip-types was introduced in Node 22.6.0, so on a stock Node 22.0–22.5 install pnpm bot:generate errors out. Since this PR makes the committed artifacts reproducible via that command, the declared Node range should either be tightened or the script should use tsx or a similar TS runner instead of relying on the flag.
Tests look legitimate: the router unit suite covers the new investigation/fix-loop transitions and actor enforcement, the workers-pool integrations exercise the full DO happy path plus rejection/expiry branches, and the drift test is a real guard rather than a tautology.
| "build": "vite build", | ||
| "deploy": "vite build && wrangler deploy", | ||
| "cf-typegen": "wrangler types", | ||
| "bot:generate": "node --experimental-strip-types scripts/generate-machine.ts", |
There was a problem hiding this comment.
[needs fixing] The bot:generate script now uses node --experimental-strip-types scripts/generate-machine.ts, but the workspace declares "node": ">=22" and this package has no local engines override. --experimental-strip-types is only available from Node 22.6.0 upwards, so on a clean Node 22.0–22.5 install this command exits with an unknown flag. Because this PR’s artifact contract depends on maintainers being able to run pnpm bot:generate, the declared runtime range needs to match the flag’s actual availability.
| "bot:generate": "node --experimental-strip-types scripts/generate-machine.ts", | |
| "bot:generate": "tsx scripts/generate-machine.ts", |
If you take that route, add tsx to this package’s devDependencies. Alternatively, keep the flag but add an engines field to this package (or bump the root workspace engines.node) to >=22.6.0 so the requirement is honest.
f0c08e1 to
f93a86e
Compare
f93a86e to
8f26cb3
Compare
f44ecb2 to
0e754db
Compare
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
docs | 0440c80 | Aug 09 2026, 11:24 AM |
Adds the maintainer-triggered investigation lane (reproduce + diagnose, no fix) and the fix loop (candidate branch -> preview build -> reporter confirm -> draft PR, else reap): seven new states, the preview.* and expire event families, and the diagnose/fix/openDraftPr/reapBranch actions. Fully additive -- every existing state, event, and transition is preserved. validateMachine passes: 17 states, 30 events, 87 transitions.
Threads the new machine states through the runtime: - comments: readonly replies for the seven states, a needs_info agent comment, and target-aware CTAs for agent.reproduced/fix_ready so the verdict and preview lanes read differently from the legacy lanes. - router: diagnose and fix modes in outcomeFromResult (verdict unclear -> needs_info; fix advances only when pushed). - orchestrator: parseInvestigateMode learns diagnose/fix; runAction dispatches openDraftPr (draft PR) and reapBranch (delete bot/fix-<n>); a 14-day reporter-silence alarm fires expire. - github: createPullRequest gains a draft option; adds deleteBranch. - investigate agent: diagnose/fix modes plumbed, with diagnose-mode prompt guidance that forbids a fix.
The machine.ts header promised generated artifacts that did not exist. Adds `pnpm bot:generate` (scripts/generate-machine.ts) which renders machine.json (runtime snapshot) and BOT_STATE_MACHINE.md (states/events/ transitions tables + mermaid diagram) deterministically from machine.ts, plus a vitest drift check that fails if the committed artifacts fall out of sync. The generator uses explicit .ts imports so node runs it without a bundler; allowImportingTsExtensions keeps tsc happy. The artifacts are excluded from oxfmt so the generator owns their format.
Router unit tests: maintainer-only authority on investigate/fix, the investigation verdicts, the fix loop through preview to the reporter wait, confirm/reject/expire from awaiting_reporter, and outcomeFromResult in diagnose/fix modes. Orchestrator integration tests drive the real DO and machine through the happy fix loop (diagnosis -> fixing -> preview_building -> awaiting_reporter -> confirmed draft PR), the reject and 14-day expire reaps, needs_info from verdict unclear, and non-maintainer rejection.
A fix-mode run that reports skipped emitted agent.skipped, which fixing had no transition for -- the event no-oped and the run was cleared, so the stale-run watchdog (which only covers runs that hang without reporting) never fired. The item wedged in fixing permanently, escapable only by an unadvertised bare reset. Adds fixing --agent.skipped--> blocked, mirroring investigating, so every reportable outcome of a fix run is covered. Regenerates the artifacts.
…boxes; typecheck the generator script
…le orchestrator and machine comments
0e754db to
0440c80
Compare
What does this PR do?
Slice 2 of the bot next-gen stack (on #2375). Extends the emdash-bot state machine with maintainer-triggered investigation and the fix loop, and makes the machine's generated-artifact contract real.
investigating→reproduced/not_reproduced/needs_info;fixing→preview_building→awaiting_reporter), 6 new events (investigate+fixcommands, maintainer-actor-only;agent.needs_info;preview.ready/preview.failed;expire), 4 new actions (investigate.diagnose,investigate.fix,openDraftPr,reapBranch). All additive;validateMachineclean at 17 states / 30 events / 88 transitions. Fix-loop shape per the design: reproduced → (maintainerfix) → candidate → preview → reporter confirms → draft PR; reject or 14-day silence reaps the branch.decision.tobranching where reused agent events now land in two targets (legacy paths keep their CTAs); investigation modes in router + agent (diagnose mode forbids fixing);openDraftPr/reapBranchon existing github helpers;expirerides the existing hourly tick with a 14-dayREPORTER_SILENCE_WINDOW_MS.machine.ts's header has always promised generatedmachine.json+BOT_STATE_MACHINE.mdwith drift checking — the generator and artifacts did not exist. This addspnpm bot:generate(deterministic renderers), commits both artifacts, and adds a byte-for-byte drift test to the unit suite.Authorization is payload-derived end-to-end: actor identity comes from the webhook, never model output, so the classifier cannot escalate a reporter into maintainer-only actions (covered by an integration test).
Closes #
Type of change
Checklist
pnpm typecheckpasses — n/a-with-note: same pre-existinginfra/emdash-botred state as chore(emdash-bot): port to Flue 2.0.3 with Workers traces (bot next-gen 1/5) #2375 (untracked in CI; zero new error classes)pnpm lintpasses — oxlint--type-aware --deny-warningsexit 0 on changed scopepnpm testpasses — unit 96/96 (incl. artifact drift check), integration 35/35 (workers pool)pnpm formathas been runAI-generated code disclosure
Screenshots / test output