Skip to content

feat(emdash-bot): maintainer-triggered investigation + fix-loop states (bot next-gen 2/5) - #2376

Open
ascorbic wants to merge 5 commits into
bot-nextgen/01-flue2-skeletonfrom
bot-nextgen/02-machine-fix-loop
Open

feat(emdash-bot): maintainer-triggered investigation + fix-loop states (bot next-gen 2/5)#2376
ascorbic wants to merge 5 commits into
bot-nextgen/01-flue2-skeletonfrom
bot-nextgen/02-machine-fix-loop

Conversation

@ascorbic

@ascorbic ascorbic commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

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.

  • Machine: 7 new states (investigatingreproduced/not_reproduced/needs_info; fixingpreview_buildingawaiting_reporter), 6 new events (investigate + fix commands, maintainer-actor-only; agent.needs_info; preview.ready/preview.failed; expire), 4 new actions (investigate.diagnose, investigate.fix, openDraftPr, reapBranch). All additive; validateMachine clean at 17 states / 30 events / 88 transitions. Fix-loop shape per the design: reproduced → (maintainer fix) → candidate → preview → reporter confirms → draft PR; reject or 14-day silence reaps the branch.
  • Consumer ripple: exhaustive-switch cases for the new states in comments; decision.to branching where reused agent events now land in two targets (legacy paths keep their CTAs); investigation modes in router + agent (diagnose mode forbids fixing); openDraftPr/reapBranch on existing github helpers; expire rides the existing hourly tick with a 14-day REPORTER_SILENCE_WINDOW_MS.
  • Artifacts contract made real: machine.ts's header has always promised generated machine.json + BOT_STATE_MACHINE.md with drift checking — the generator and artifacts did not exist. This adds pnpm 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

Maintainer-directed work: implements the bot next-gen design authored with/approved by the maintainer; no separate Discussion.

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes — n/a-with-note: same pre-existing infra/emdash-bot red 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 lint passes — oxlint --type-aware --deny-warnings exit 0 on changed scope
  • pnpm test passes — unit 96/96 (incl. artifact drift check), integration 35/35 (workers pool)
  • pnpm format has been run
  • I have added/updated tests for my changes — authority rejection, full fix-loop happy path, reject + expire reaps, needs_info, and a regression test for a skipped fix-mode run (verified to fail without its transition)
  • User-visible strings in the admin UI are wrapped for translation — n/a (bot comments are not admin UI)
  • I have added a changeset — n/a, unpublished infra worker
  • New features link to an approved Discussion — see note above

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Fable 5 (Claude Code; built and adversarially reviewed by Fable subagents)

Screenshots / test output

unit:        96 passed (96)
integration: 35 passed (35)
validateMachine: 17 states / 30 events / 88 transitions, 0 problems

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.
@changeset-bot

changeset-bot Bot commented Aug 8, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 628fd7a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@ascorbic ascorbic changed the title bot nextgen/02 machine fix loop feat(emdash-bot): maintainer-triggered investigation + fix-loop states (bot next-gen 2/5) Aug 8, 2026
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-cache 628fd7a Aug 08 2026, 01:23 PM

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground 628fd7a Aug 08 2026, 01:23 PM

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-do 628fd7a Aug 08 2026, 01:23 PM

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Scope check

This 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.

@pkg-pr-new

pkg-pr-new Bot commented Aug 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@2376

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@2376

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@2376

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@2376

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@2376

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@2376

emdash

npm i https://pkg.pr.new/emdash@2376

create-emdash

npm i https://pkg.pr.new/create-emdash@2376

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@2376

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@2376

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@2376

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@2376

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@2376

@emdash-cms/registry-verification

npm i https://pkg.pr.new/@emdash-cms/registry-verification@2376

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@2376

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@2376

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@2376

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@2376

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@2376

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@2376

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@2376

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@2376

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@2376

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@2376

commit: 628fd7a

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:generate script, 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",
Comment on lines +11 to +14
const botRoot = fileURLToPath(new URL("..", import.meta.url));

writeFileSync(`${botRoot}.flue/lib/machine.json`, renderMachineJson());
writeFileSync(`${botRoot}BOT_STATE_MACHINE.md`, renderMachineDoc());
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-review No maintainer or bot review yet labels Aug 8, 2026
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Aug 8, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Suggested change
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([]);
});

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

overlap review/awaiting-author Reviewed; waiting on the author to respond size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants