Skip to content
Open
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
36 changes: 36 additions & 0 deletions .github/workflows/deploy-cloudflare.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Deploy to Cloudflare Workers

on:
workflow_dispatch:
inputs:
environment:
description: "Deployment environment"
required: false
default: "production"
type: choice
options:
- production
- preview

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "18"
cache: "npm"

- name: Install dependencies
run: npm install

- name: Build frontend
run: npm run build

- name: Deploy to Cloudflare Workers
working-directory: apps/proof-cloudflare
run: npx wrangler deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ npm-debug.log*
# Scratch
.tmp/
.worktrees/

# Wrangler (Cloudflare dev artifacts)
.wrangler/
build-release/
build-release-dist/

Expand Down
4 changes: 3 additions & 1 deletion AGENT_CONTRACT.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Agent Contract: Direct Markdown Sharing

This contract defines the public Proof SDK flow for creating and operating on shared documents over HTTP.
This contract defines the public Proof SDK flow for creating and operating on shared documents over HTTP. The routes and semantics apply to all deployment targets (Express server, Cloudflare Workers, etc.) — replace `localhost:4000` with your deployment URL.

## Endpoints

Expand Down Expand Up @@ -152,3 +152,5 @@ curl -X POST http://localhost:4000/documents \
-H "Content-Type: application/json" \
-d '{"markdown":"# Plan\n\nShip the rewrite.","title":"Rewrite Plan","role":"commenter"}'
```

See `docs/DEPLOYMENT.md` for deployment options and configuration.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ If you want the hosted product, use [Proof](https://proofeditor.ai).
- `packages/doc-store-sqlite`
- `packages/agent-bridge`
- `apps/proof-example`
- `apps/proof-cloudflare`
- `server`
- `src`

Expand Down Expand Up @@ -89,8 +90,10 @@ npm test

- `AGENT_CONTRACT.md`
- `docs/agent-docs.md`
- `docs/DEPLOYMENT.md`
- `docs/proof.SKILL.md`
- `docs/adr/2026-03-proof-sdk-public-core.md`
- `docs/adr/2026-03-cloudflare-workers-deployment.md`

## License

Expand Down
135 changes: 135 additions & 0 deletions apps/proof-cloudflare/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Proof Cloudflare

Cloudflare Workers + Durable Objects deployment target for the Proof SDK. Same editor, same agent bridge, different runtime.

Each document gets its own Durable Object instance with embedded SQLite, Yjs collab, and WebSocket handling. D1 stores the cross-document catalog (slug-to-DO mapping).

## Quick Start

Prerequisites: Node.js 18+, Cloudflare account with Workers Paid ($5/mo), Wrangler CLI.

```bash
# From repo root
npm install && npm run build

# Create D1 database
wrangler d1 create proof-catalog
# Update database_id in wrangler.jsonc with the returned ID

# Run migration
cd apps/proof-cloudflare
wrangler d1 migrations apply proof-catalog --remote

# Deploy
npx wrangler deploy
```

## Local Development

```bash
npm run build # build frontend assets (from repo root)
cd apps/proof-cloudflare
npm run dev # Miniflare with D1 + DO simulation
```

## Architecture

```
Worker (index.ts)
├── GET / → create doc, redirect to /d/:slug
├── GET /d/:slug → serve SPA (rewrite asset paths)
├── POST /documents → API doc creation
├── POST /share/markdown → create from raw markdown
├── GET /health → 200 OK
├── GET /.well-known/agent.json → agent discovery
├── /api/agent/:slug/* → route to Durable Object
├── /documents/:slug/* → route to Durable Object
└── /ws/:slug → route to Durable Object

DocumentSession (Durable Object)
├── Yjs Y.Doc with SQLite persistence + compaction
├── Hocuspocus WebSocket protocol (auth, sync, awareness)
├── Agent bridge HTTP routes (see table below)
└── DODocumentStorage (events, idempotency, access control)
```

## Route Parity with Express

The CF Worker implements the full agent bridge contract. Document lifecycle routes are also supported. Some Express-specific routes (collab management, legacy API paths) are not applicable to the DO architecture.

### Agent Bridge (full parity)

| Route | Method | Express | CF Worker |
|-------|--------|---------|-----------|
| `/api/agent/:slug/state` | GET | Y | Y |
| `/api/agent/:slug/snapshot` | GET | Y | Y |
| `/api/agent/:slug/edit` | POST | Y | Y |
| `/api/agent/:slug/edit/v2` | POST | Y | Y |
| `/api/agent/:slug/rewrite` | POST | Y | Y |
| `/api/agent/:slug/ops` | POST | Y | Y |
| `/api/agent/:slug/marks/comment` | POST | Y | Y |
| `/api/agent/:slug/marks/suggest-replace` | POST | Y | Y |
| `/api/agent/:slug/marks/suggest-insert` | POST | Y | Y |
| `/api/agent/:slug/marks/suggest-delete` | POST | Y | Y |
| `/api/agent/:slug/marks/accept` | POST | Y | Y |
| `/api/agent/:slug/marks/reject` | POST | Y | Y |
| `/api/agent/:slug/marks/reply` | POST | Y | Y |
| `/api/agent/:slug/marks/resolve` | POST | Y | Y |
| `/api/agent/:slug/marks/unresolve` | POST | Y | Y |
| `/api/agent/:slug/presence` | POST | Y | Y |
| `/api/agent/:slug/presence/disconnect` | POST | Y | Y |
| `/api/agent/:slug/events/pending` | GET | Y | Y |
| `/api/agent/:slug/events/ack` | POST | Y | Y |
| `/api/agent/:slug/repair` | POST | Y | Y |
| `/api/agent/:slug/clone-from-canonical` | POST | Y | Y |

### Document Routes

| Route | Method | Express | CF Worker | Notes |
|-------|--------|---------|-----------|-------|
| `POST /documents` | POST | Y | Y | |
| `POST /share/markdown` | POST | Y | Y | |
| `/documents/:slug/state` | GET | Y | Y | via DO |
| `/documents/:slug/snapshot` | GET | Y | Y | via DO |
| `/documents/:slug/content` | GET | Y | Y | via DO |
| `/documents/:slug/open-context` | GET | Y | Y | via DO |
| `/documents/:slug/collab-session` | GET | Y | Y | via DO |
| `/documents/:slug/collab-refresh` | POST | Y | Y | via DO |
| `/documents/:slug/events/pending` | GET | Y | Y | via DO |
| `/documents/:slug/events/ack` | POST | Y | Y | via DO |
| `/documents/:slug/pause` | POST | Y | Y | via DO |
| `/documents/:slug/resume` | POST | Y | Y | via DO |
| `/documents/:slug/revoke` | POST | Y | Y | via DO |
| `/documents/:slug/delete` | POST | Y | Y | via DO |
| `/documents/:slug/title` | PUT | Y | Y | via DO |

### Express-Only Routes (not in CF Worker)

| Route | Reason |
|-------|--------|
| `/api/capabilities` | Express middleware concern |
| `/d/:slug/bridge/*` | Neutral bridge mount — agents use `/api/agent/` or `/documents/` |
| `/api/documents` (legacy) | Legacy create route — use `POST /documents` |
| Collab management endpoints | DO handles collab internally; no external collab server to manage |

## Files

| File | Purpose |
|------|---------|
| `src/index.ts` | Worker entrypoint — routing, doc creation, SPA serving |
| `src/document-session.ts` | Durable Object — Yjs sync, agent routes, marks, edits |
| `src/document-engine.ts` | Marks CRUD (comment, suggest, accept/reject, reply, resolve) |
| `src/canonical-projection.ts` | Y.Doc <-> markdown via ProseMirror |
| `src/milkdown-headless.ts` | Headless Milkdown engine for Workers runtime |
| `src/storage-do.ts` | SQLite-backed events, idempotency, access control |
| `src/document-ops.ts` | Operation parsing and authorization for `ops` endpoint |
| `src/agent-edit-ops.ts` | Text-level edit operations (append, replace, insert) |
| `src/auth.ts` | Token resolution and role-based access |
| `src/idempotency.ts` | Mutation replay detection |
| `src/proof-span-strip.ts` | Proof span tag stripping for agent-facing markdown |

## See Also

- `docs/DEPLOYMENT.md` — full deployment guide (Express and Workers)
- `docs/adr/2026-03-cloudflare-workers-deployment.md` — decision record
- `AGENT_CONTRACT.md` — agent HTTP protocol
10 changes: 10 additions & 0 deletions apps/proof-cloudflare/d1/migrations/0001_catalog.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE TABLE documents (
id TEXT PRIMARY KEY,
slug TEXT UNIQUE NOT NULL,
title TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
do_id TEXT NOT NULL
);

CREATE INDEX idx_documents_slug ON documents(slug);
32 changes: 32 additions & 0 deletions apps/proof-cloudflare/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "proof-cloudflare",
"version": "0.1.0",
"type": "module",
"private": true,
"scripts": {
"dev": "wrangler dev",
"deploy": "wrangler deploy"
},
"dependencies": {
"@milkdown/core": "^7.5.0",
"@milkdown/kit": "^7.5.0",
"@milkdown/preset-commonmark": "^7.5.0",
"@milkdown/preset-gfm": "^7.5.0",
"@milkdown/prose": "^7.5.0",
"@milkdown/transformer": "^7.5.0",
"lib0": "^0.2.99",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
"remark-parse": "^11.0.0",
"remark-stringify": "^11.0.0",
"unified": "^11.0.0",
"y-prosemirror": "^1.2.19",
"y-protocols": "^1.0.6",
"yjs": "^13.6.0"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250109.0",
"wrangler": "^4.0.0",
"typescript": "^5.3.0"
}
}
Loading