Pact — Devpost Story
Inspiration
Every B2B engagement I have ever worked on, as a consultant, as an engineer delivering projects, as someone managing vendor relationships, ends up in the same place: an email chain.
"Can you confirm you received the files?" "Did you sign off on the deliverable?" "I thought you said you accepted this last week?"
There is no formal moment where both sides simultaneously know that an obligation has been met. There is no automation that fires when all conditions are satisfied. There is no immutable record of who did what and when.
I watched a colleague lose a £40,000 contract dispute because there was no formal acceptance record, just a thread of emails where a client claimed they "hadn't formally agreed." The work was done. The files were delivered. But the proof wasn't there.
That is the problem Pact solves: the gap between doing the work and proving it was accepted.
The mental model I kept coming back to: Pact is to business coordination what Stripe is to payments. Stripe doesn't tell you what to sell. Pact doesn't tell you what to agree on. Both give you the infrastructure to execute reliably, automatically, atomically, with a paper trail.
What it does
Pact is a multi-party commitment execution platform. Any two or more parties define their obligations, accept them, fulfil them, and the outcome fires automatically the moment all conditions are met.
The core flow:
- A creator defines a Pact, title, outcome statement, parties, and what each party must do
- Counterparties receive a branded invite email and accept via a secure link
- Each party fulfils their assigned condition (with an optional note or reference URL as evidence)
- When the last condition is fulfilled, Aurora DSQL fires a single ACID transaction, the pact is marked
EXECUTEDsimultaneously for all parties, a cryptographic audit entry is written, and all connected browsers update in real time via Server-Sent Events - An immutable public Execution Receipt is generated, shareable proof with parties, conditions, execution timestamp, and a SHA-256 fingerprint
Real-world use cases:
- Freelancers — Submit a deliverable → Client formally accepts → Project closes with a tamper-evident audit trail. No more "I never agreed to this."
- Vendors — Confirm shipment → Buyer confirms receipt → Invoice automatically approved. No more chasing confirmations.
- Partnerships — Agency reaches a milestone → Client signs off → Next project phase unlocks. Both sides know immediately.
Every state transition is recorded in an append-only audit log where each entry includes a SHA-256 hash of itself plus the previous entry's hash, a cryptographic chain that makes altering the history mathematically detectable.
How we built it
Stack: Next.js 16 App Router on Vercel · Amazon Aurora DSQL via OIDC IAM authentication · Drizzle ORM · Vercel KV (Upstash Redis) for SSE fan-out · Auth.js v5 with magic-link via Resend · Custom design system (Vibrant Authority, Playfair Display, Hanken Grotesk, electric lime #c3f400)
Phase 1 — Foundation
Established the Aurora DSQL connection using AuroraDSQLPool with the Vercel OIDC credentials provider — the database authenticates via IAM role assumption, not a password. No static credentials anywhere in the codebase. Built an 11-table schema including executions with UNIQUE(pact_id) (database-level idempotency) and audit_log (append-only SHA-256 hash chain). All status fields use VARCHAR with CHECK constraints rather than PostgreSQL enums, a DSQL compatibility requirement.
Phase 2 — Core Loop
Built the end-to-end flow: create pact → invite parties → accept via token → fulfil conditions → atomic execution. The centrepiece is lib/execution.ts:
1. Pre-flight: count pending conditions (read-only, no lock)
2. UPDATE pacts SET status='EXECUTED' WHERE status='ACTIVE' ← race guard via OCC
3. INSERT INTO executions (UNIQUE constraint = idempotency) ← database-level guarantee
4. INSERT INTO audit_log with SHA-256 hash chain entry
5. COMMIT
6. Post-commit: broadcast SSE event + send emails (never inside the transaction)
Every write is wrapped in withDsqlRetry — catching Aurora DSQL OCC error codes 40001, OC000, and OC001 with exponential backoff. This is not a workaround. It is the correct production pattern for a distributed ACID database that uses Optimistic Concurrency Control.
Phase 3 — Real-time
Vercel serverless functions are stateless. An in-memory Map of SSE listeners breaks across function instances. If Party A connects to instance X and Party B's fulfilment is processed by instance Y, Party A never receives the event. Solved with Vercel KV as a cross-instance event queue: kv.rpush() on publish, kv.lrange() with a cursor on the SSE endpoint. ~1 second latency, correct across all instances.
Phase 4 — Design and Polish
The Vibrant Authority design system, deep ink blacks, electric lime accent used only for execution moments and CTAs, particle canvas landing page animation, spring-eased PACT EXECUTED banner, SHA-256 fingerprint on the public Execution Receipt page. Email templates in React Email, delivered via Resend from noreply@usepact.dev.
Challenges we ran into
1. Aurora DSQL is not Aurora PostgreSQL
The Drizzle CLI cannot connect to DSQL locally without live OIDC credentials; all migrations had to be run by pasting SQL directly into the AWS Query Editor. DSQL also does not support PostgreSQL ENUM types; all status columns required VARCHAR with CHECK constraints.
2. The Edge / Node.js boundary
Next.js middleware runs in the Edge runtime. The original middleware.ts imported auth, which imported lib/db/index.ts, which imported pg and @aws/aurora-dsql-node-postgres-connector, packages that use Node.js native modules (dns, net, tls) unavailable in Edge. Solution: split the auth config into auth.config.ts (Edge-safe, no database) for middleware, and auth.ts (full config with DrizzleAdapter) for API routes and server components.
3. @vercel/kv has no pub/sub
The architecture plan assumed kv.subscribe() for SSE fan-out. @vercel/kv@3.0 is built on the Upstash REST API, which has no pub/sub support. Rebuilt as a polling-based event queue: kv.rpush() on publish, kv.lrange() with a cursor on the SSE stream endpoint.
4. Double JSON serialisation in KV
@vercel/kv automatically serialises objects to JSON. Calling JSON.stringify(event) before kv.rpush() created a double-encoded string. On retrieval, the already-parsed object was passed to JSON.parse(), which coerces an object to "[object Object]", not valid JSON. Fix: pass the object directly to rpush, type lrange as PactSSEEvent.
5. JWT vs database sessions in the Edge
Auth.js with DrizzleAdapter defaults to database sessions. The Edge middleware config (no database adapter) defaulted to JWT mode, causing JWEInvalid: Invalid Compact JWE errors when validating database session cookies. Solution: explicitly set session: { strategy: 'jwt' } in auth.config.ts, sessions stored as encrypted JWE cookies, validatable in both runtimes without a database round-trip.
Accomplishments that we're proud of
The execution moment is real. Two browsers, two parties, one click, and both screens show PACT EXECUTED simultaneously. That is not a demo trick. It is Aurora DSQL's distributed ACID transaction firing across tables, broadcast via SSE to all connected clients via Vercel KV. The guarantee is at the database level, not the application level.
Zero static credentials, zero hardcoded secrets. The entire database authentication chain uses OIDC. Vercel exchanges the OIDC token for temporary AWS credentials at runtime, which are used to sign DSQL connection tokens. AUTH_SECRET is the only long-lived secret, and it is generated, not guessable.
The audit chain is genuinely tamper-evident. Each audit_log entry hashes its own content plus the previous entry's hash. You cannot alter any entry without invalidating every subsequent hash in the chain. This is stored in Aurora DSQL; the source of truth is the distributed database, not a centralised log server.
The OCC implementation is production-correct. Most applications treat database serialisation errors as fatal. withDsqlRetry catches 40001, OC000, and OC001 specifically and retries with exponential backoff. This is the pattern recommended for DSQL, implemented deliberately.
What we learned
- Aurora DSQL's OCC model is architecturally different from pessimistic locking. There is no
SELECT FOR UPDATE. Conflict detection happens at commit time. The retry pattern is not an edge case; it is the design. - The Edge/Node.js boundary in Next.js is a hard architectural constraint. Any Node.js-only dependency in a Client Component's import chain will fail the build. Database connections must never leak into client-side code.
- OIDC credential-free authentication is the right default. No rotation schedule, no exposure window, no secrets in environment variables beyond what Auth.js requires.
- Design systems have compounding returns. Defining the Vibrant Authority token set upfront meant every subsequent page was faster to build, more consistent, and easier to reason about.
- The hardest part of a hackathon is not the code. It is knowing when to stop adding features and start completing the submission.
What's next for Pact
Pact V1 proves the atomic execution model works at a real product level. V2 roadmap:
- Contract document upload — PDF upload via Vercel Blob, AI-powered milestone extraction (parse a real contract and auto-populate the conditions and parties)
- Sequential milestone gating — Pacts where milestones unlock only after the previous one executes, with the same ACID execution guarantee
- Webhook triggers — POST to an external URL on execution: payment release, Slack notification, workflow automation
- Stripe escrow integration — Hold funds in escrow, release automatically on pact execution
- Developer API — Programmatic pact creation for teams building commitment workflows into their own products
Aurora DSQL's active-active multi-region write capability means the platform is already designed for enterprise-scale: a consultant in Lagos, a client in London, and a reviewer in New York can all write simultaneously, with the same ACID execution guarantee, at latencies appropriate for each region.
Built With
- amazon-web-services
- authjs
- drizzleorm
- dsql
- kv
- nextjs
- react
- tailwind
- typescript
- vercel
Log in or sign up for Devpost to join the conversation.