---
title: "Publish a Draft"
description: "Configure the GitHub extension to permit draft pull requests only, package the factory's evidence for reviewers, and deploy the eve application to Vercel."
canonical_url: "https://vercel.com/academy/creating-a-software-factory/publish-a-draft"
md_url: "https://vercel.com/academy/creating-a-software-factory/publish-a-draft.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-08-29T01:48:00.789Z"
content_type: "lesson"
course: "creating-a-software-factory"
course_title: "Creating a Software Factory"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Publish a Draft

# Publish a draft

After independent verification, the factory may open a draft pull request. Policy still denies ready pull requests, and the factory has no merge capability.

```ts
if (input?.draft === true) return "not-applicable";
return { reason: "The factory may create draft pull requests only.", type: "denied" };
```

A draft carries the real diff and decision evidence. Final authority remains with a person.

## Ship Proof, Keep Merge Authority

Deploy the factory and allow it to publish independently verified draft pull requests only.

## Hands-on Exercise 5.2

Create `agent/lib/github/approval.ts`:

```ts title="agent/lib/github/approval.ts"
import type { ApprovalContext, ApprovalStatus } from "eve/tools";

export function createPullRequestPolicy(ctx: ApprovalContext): ApprovalStatus {
  const input = ctx.toolInput as { draft?: unknown } | undefined;
  if (input?.draft === true) {
    return "not-applicable";
  }
  return {
    reason: "The factory may create draft pull requests only.",
    type: "denied",
  };
}
```

Create `agent/extensions/github.ts` and include only the repository tools the orchestrator needs. Connect `createPullRequest` to the policy:

```ts title="agent/extensions/github.ts"
import githubExtension from "@github-tools/eve-extension";
import { factoryRepo } from "../lib/config.js";
import { createPullRequestPolicy } from "../lib/github/approval.js";
import { GITHUB_CONNECTOR } from "../lib/github/credentials.js";

export default githubExtension({
  connector: GITHUB_CONNECTOR,
  context: factoryRepo,
  include: [
    "getRepository",
    "getIssueContext",
    "listIssueComments",
    "listBranches",
    "getPullRequestContext",
    "listPullRequestFiles",
    "createPullRequest",
  ],
  requireApproval: {
    createPullRequest: createPullRequestPolicy,
  },
});
```

`factoryRepo` supplies `{ owner, repo }`; `GITHUB_CONNECTOR` names the attached connector. The `include` list is the complete GitHub capability set, with no stored personal token.

Add two tests at `agent/lib/github/approval.test.ts`: drafts return `"not-applicable"`, while a ready pull request returns `{ type: "denied" }`.

Finish `agent/instructions.md` with the pull request body requirements. Include the problem statement, acceptance criteria with verification results, commands run, risks, deviations, and a link to the issue. Call `github__createPullRequest` with `draft: true` only after the Verifier approves.

The application was linked, given AI Gateway credentials, and attached to GitHub in Lesson 3.1. Deploy that linked application now:

```bash
pnpm exec eve deploy
```

Use the same personal GitHub repository configured for the sandboxes. The deployment preserves the channel, tools, subagents, approval policy, and durable session behavior.

## Try It

Run the local policy checks before testing the deployed factory:

```bash
pnpm test agent/lib/github/approval.test.ts
pnpm typecheck
pnpm exec eve info
```

Create an issue from `fixtures/issues/bug-example.md`, then add the `factory` label from an account with repository triage access or higher. Follow the run through verification. Its pull request should be a draft with evidence for every criterion.

If nothing starts, verify each connection in order:

```bash
vercel connect list
pnpm exec eve info
```

Confirm the connector targets `/eve/v1/github`, its UID matches `GITHUB_CONNECTOR`, the App can access the repository, and the deployment includes `agent/channels/github.ts`.

Attempt a ready pull request through the policy test. It must be denied. Confirm that the extension exposes no merge tool.

\*\*Note: Review from the pull request only\*\*

Pretend the agent conversation is unavailable. Can you decide what to inspect from the issue link, diff, criteria, commands, and verification report in the draft body?

\*\*Warning: The pull request opens ready\*\*

Pass `draft: true` and keep the policy test. Prompt instructions alone cannot enforce a draft-only boundary.

\*\*Warning: The repository belongs to an organization\*\*

Vercel Hobby projects require the course repository under your personal GitHub account.

## Commit

```bash
git add agent/extensions agent/lib/github agent/instructions.md
git commit -m "feat(factory): publish verified drafts"
```

## Done-When

- [ ] The GitHub extension exposes no merge capability
- [ ] Ready pull requests fail the policy test
- [ ] Draft creation happens only after verification approval
- [ ] The draft body carries the specification and evidence
- [ ] The deployed factory processes a labeled GitHub issue

The factory can now produce reviewable work while merge authority remains external. The final lesson turns failed decisions into regression evaluations.

## Solution

The complete pull request policy is shown in the exercise. Its test is:

```ts title="agent/lib/github/approval.test.ts"
import { describe, expect, it } from "vitest";
import { createPullRequestPolicy } from "./approval.js";

describe("createPullRequestPolicy", () => {
  it("permits draft pull requests", () => {
    expect(createPullRequestPolicy({ toolInput: { draft: true } } as never))
      .toBe("not-applicable");
  });

  it("denies pull requests that are ready for review", () => {
    expect(createPullRequestPolicy({ toolInput: { draft: false } } as never))
      .toMatchObject({ type: "denied" });
  });
});
```


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
