TURNSTILE · built by forge
A demonstration of agent-driven software delivery

Turnstile

A backend ticket-sales service for small events — designed, decided, coded and tested end-to-end by the forge pipeline, from a single paragraph of product idea. A human only reviewed and approved decisions.

9,156 lines of Go 19 ADRs 12 kanban cards 237 test functions 3 bounded contexts ~4h agent session
Use ← / → (or Space) to navigate · 12 slides
Where it started

01 One paragraph of intent

The whole system was grown from a plain-language brief — no schemas, no API spec, no diagrams. forge extracted the domain, surfaced the decisions, and built the code.

  • Buyer reserves 1–4 seats; a hold lasts 10 minutes.
  • Payment is async via an external provider; the callback can be delayed up to 15 min and duplicated.
  • Cancel & refund up to 48h before the event.
  • A buyer may never hold more than 4 tickets per event.
  • P95 < 200 ms at 100 concurrent reservations.
"Never, under any circumstances, sell or hold more seats than the hall's capacity — this is the most important requirement."

docs/idea.md, the only human-authored spec

The forge pipeline

02 Idea → model → decisions → code

Every stage left a durable, inspectable artifact under meta/. The trail is part of the deliverable — you can audit how each line of code traces back to intent.

StageWhat forge producedArtifact
Brainstorm → domainvision, user stories, formal domain model (events, commands, aggregates, policies, glossary)inputs/ · domain/
Capabilities & contextsbusiness capabilities, bounded-context mapdomain/
Decisions19 ADRs in Nygard format, each with rejected alternativesdecisions/adr/
SynthesisC4 diagrams + a requirement→component→test trace matrixc4/ · trace.yml
Kanban12 cards across waves, each built in its own git worktree, with review cycleskanban/
Docslayered "how it works" + OpenAPI contractdocs/
Architecture

03 A modular monolith, consistent down to the code

One Go deployable; contexts isolated by package and by Postgres schema; cross-context communication only through a transactional outbox. Each choice is pinned to an ADR — and the code follows it.

CTX-001 · Sales

Events, reservations, the capacity guard, payment intake, hold expiry, reopen-on-release.

CTX-002 · Fulfilment

Orders, tickets, the decoupled ticket-email worker.

CTX-003 · Refund

Cancellation, refund requests with retry & escalation.

Platform

Outbox relay, scheduler sweep, append-only audit log, injectable UTC clock.

Patterns

Anti-corruption layers to providers · dedicated idempotency store · ports & adapters.

Discipline

Schema-per-context (ADR-0003) · async edges via outbox (ADR-0004) · single clock (ADR-0019).

The crown jewel

04 Oversell made structurally impossible

The single most important requirement is enforced by one guarded atomic UPDATE — the only write-path that can increment held_seats. No application-level read-modify-write, no retry loop.

  • The guard and the count live in one indivisible statement.
  • Postgres' row write-lock serializes all concurrent reservers of an event.
  • The payment-provider call is deliberately outside the critical section.
  • Documented TOCTOU fix for the per-buyer cap on the same lock.
UPDATE inventory.events
SET held_seats = held_seats + $3
WHERE id = $1
  AND status = 'published'
  AND sales_open_at <= $2
  AND sales_close_at > $2
  AND held_seats + sold_seats + $3 <= capacity
-- 0 rows affected ⇒ rejected. Oversell
-- is impossible by construction (NFR-002).
Tests are not theatre

05 237 test functions that actually bite

Concurrency, proven

200 buyers race for 100 seats → exactly 100 succeed, held_seats == 100, and the reservation rows are reconciled against the successes — no phantom inserts.

Real Postgres

The never-oversell & per-buyer proofs run behind an integration build tag against a live database — properties that cannot be faked against a mock.

Failure injection

The refund path is tested through transient failures, persistent failures, lease re-claims and escalation — not just the happy case.

End-to-end

A full reserve → pay → confirm → order flow, plus the late-callback auto-refund and the cancellation→refund→release chain.

The planted contradiction

06 10 minutes vs 15 minutes — caught, not glossed

The brief hides a trap: the 10-minute hold is shorter than the maximum 15-minute callback delay. A payment can confirm after the seats were released.

forge didn't silently pick one. It surfaced the tension as a decision, then resolved it in code: a late callback re-checks availability and either fulfils or auto-refunds — the buyer is never charged for a ticket that can't be delivered.

DEC-001 → resolved

"The 10-minute hold window is shorter than the maximum 15-minute callback delay…"

Resolved via the late-callback reconciliation policy (ADR-0007 auto-refund) and implemented in a dedicated card with concurrency tests.

Independent review

07 The verdict

"Strong-senior work, and in places above. The architecture is consistent from decision to code."

— an independent audit from a separate source

  • "Every choice is pinned in an ADR with alternatives, and the code really follows them."
  • "holdSeatsSQL with its transaction-order & TOCTOU note is an exemplary fragment."
  • "Tests aren't props: the concurrency test is strict, with failure injection and e2e flows."
  • The review trail itself is evidence: cycle 1 caught "migration runner not transactional" → fixed in cycle 2.
The honest part

08 …and where the AI drifted inside its own pipeline

The audit's two most serious findings weren't sloppiness in the code — they were organic drift between model and code: exactly the failure class the framework exists to catch.

FOUND

① INV-003 implemented short of its ADR

ADR-0015 says the 4-ticket cap counts held + confirmed. The code counted only held — a card was closed with a "finish later" comment, yet the trace marked the requirement covered. A buyer with 4 paid tickets could hold 4 more.

FOUND

② The trace matrix went stale

7 of 53 referenced tests no longer existed under those names (renamed in the email-worker refactor; coverage existed, links were dead), and 2 of 26 code-globs pointed at directories that were never used.

Found → fixed

09 Every finding, resolved — on the record

Audit findingResolutionStatusCommit
INV-003 counts only held seatsCount held + paid; honest tests that fail on the old queryFIXEDe567a3e
Trace matrix stale (7 names, 2 globs)Re-synced to real test names & inventory/·refunds/FIXEDa3f62ec
Validators let both drifts throughClosed two blind spots: self-referencing test check & dead glob matcher; added a spec↔test↔code review lensFIXEDforge a92b1f4 · 2ab938e
No CI to fail the build on driftGitHub Actions drift-gate workflowFIXED18879dd
One-line README, no one-command runFull README + generated Makefile & docker-compose (make run)FIXED81cc903 · e362f3c
Auth is a header stub; webhook unsignedExplicit "demonstration · no security hardening" disclaimer in READMENOTED81cc903
The strategic takeaway

10 The drift is the point

Findings ①–② aren't a defect to quietly sweep away — they're the most convincing content the project could have produced: real drift, caught and fixed by the very framework built to catch it.

Two genuine "where AI errs even inside the pipeline" cases beat any hypothetical: "closed a card with a 'do it later' comment and marked the requirement covered" and "didn't re-sync the trace after a refactor."

forge now catches this class

  • Aspirational-green — a review lens checks a test's body really exercises its AC.
  • Deferral capture — a "later card" comment becomes a tracked backlog item.
  • Dead trace links — named tests must exist in code, not in the model that names them.
  • Glob drift — a stale code path is flagged against its resolving siblings.
  • CI red on drift → resync → green.
Read the code — and the trail

An artifact, not a black box.

Turnstile is a teaching example: a real backend whose every decision, requirement, test and even its mistakes are inspectable end to end. That transparency — including the drift it caught in itself — is the whole point.

📄 docs/idea.md — the brief 🧭 meta/architecture/ — model & ADRs 🗺 meta/architecture/trace.yml — traceability 📓 meta/kanban/retro.md — the build retro
1 / 12