This guide preserves the engineering setup, commands and conventions formerly in the root README. Start with the README for product context and the demo.
| Layer | Choice |
|---|---|
| Runtime | Node.js 24, pnpm 10 workspaces, Turborepo, strict TypeScript |
| Web | Next.js App Router (React Server Components, Server Functions, Suspense), Tailwind CSS v4 |
| API | Hono mounted inside Next.js at /api/v1; Zod schemas generate OpenAPI 3.1 and a typed openapi-fetch client |
| Data | Supabase managed Postgres as the single commerce system of record; Drizzle for typed schema and repositories |
| Migrations | Reviewed SQL under supabase/migrations is canonical and append-only; pgTAP tests run against a from-zero rebuild |
| Identity | WorkOS AuthKit; authorization enforced in the API and domain layers, with row-level security as defense in depth |
| Payments | Stripe as the billing and payments engine |
| Workflows | Durable jobs, schedules, waits, bounded retries and idempotency, on Trigger.dev or on SQS (CLOCKWORK_TASK_RUNTIME) |
| Documents | React PDF, deterministic output, immutable S3-style evidence storage |
| Everything else | E-signature, CRM, provisioning, accounting, screening, support, and marketplaces cross typed provider ports with behaviorally realistic fakes |
Two rules shape most of the code: every write goes through an authorized database transaction, a repository operation, and an atomic audit/outbox append; and no external provider is called except through a typed port that has a deterministic fake. That is why the whole test suite runs before any real credential exists.
clockwork/
├── apps/web/ # Next.js portal, admin, API and webhook entrypoints
├── packages/api/ # Hono route groups and the authorization boundary
├── packages/contracts/ # Zod schemas, OpenAPI, events, provider contracts
├── packages/domain/ # Pure state machines and commerce rules
├── packages/db/ # Drizzle schema, repositories, transactions, outbox
├── packages/integrations/ # Stripe, WorkOS, e-sign, CRM, accounting adapters
├── packages/workflows/ # Background tasks, workflows and schedules
├── packages/documents/ # Quotes, order forms, statements, certificates
├── packages/ui/ # Design system and Storybook
├── packages/testing/ # Fakes, fixtures, personas, builders, demo reset
├── supabase/ # Config, canonical migrations, seed, pgTAP tests
├── scripts/ # Release orchestration, baselines, traceability
├── docs/ # ADRs, runbooks, gate register, handoffs
└── commerce_platform_spec.md # The controlling product specification
Module boundaries are enforced, not just documented — pnpm boundaries runs
dependency-cruiser over packages and apps/web in CI.
Prerequisites: Node 24 (.nvmrc / .node-version pin the exact version),
pnpm 10 via corepack, Docker (for the local Supabase stack), and the Supabase
CLI, which is installed as a dev dependency.
corepack enable
pnpm install --frozen-lockfile
cp .env.example .env.local # annotated defaults; no real secrets needed
pnpm db:start # local Supabase (Postgres + services)
pnpm db:reset # rebuild from zero, apply migrations, seed
pnpm dev # http://localhost:3000pnpm db:reset recreates Postgres from nothing, applies the canonical SQL under
supabase/migrations, and loads supabase/seed.sql. The seed covers direct,
referral, resale, distributor/two-tier, white-label, marketplace, POC, overdue,
renewal and non-renewal, amendment, dispute, and retention-locked states — so
every surface has something real to show on first run.
Other useful entry points:
pnpm storybook # design system at :6006
pnpm db:stop # stop the local stack.env.example is the complete, annotated list of variables and is safe to copy
verbatim for local work — the placeholders are unset, and local tests use
deterministic fakes rather than live providers. A few worth knowing:
| Variable | Purpose |
|---|---|
DATABASE_URL / CLOCKWORK_SERVICE_DATABASE_URL |
Separate SSL Supavisor transaction-mode URLs for the two restricted application roles |
DIRECT_DATABASE_URL |
Accepted by migration tooling only, never by the runtime |
AUTHORIZATION_CONTEXT_SECRET / ..._SECRET_ID |
Signs the tenant authorization context; supports overlap-safe rotation |
CLOCKWORK_EXPERIENCE_ADAPTER |
database for real data, demo for deterministic fixtures |
CLOCKWORK_TASK_RUNTIME |
trigger for Trigger.dev Cloud, sqs for the queue and in-process poller |
WORKFLOWS_QUEUE_ID |
The task queue URL, injected by the deployment; read by the sqs runtime only |
Production never uses postgres or service_role; runtime roles are
NOBYPASSRLS, and browser bundles receive no database credentials. See
docs/foundation-handoff.md for the managed-project
role setup and secret-rotation procedure.
The repository ships a guided demo mode that runs the full portal against
deterministic fixtures — no Supabase, WorkOS, or Stripe credentials, and no
database. Set these in .env.local and run pnpm dev, then open
http://localhost:3000/demo:
CLOCKWORK_EXPERIENCE_ADAPTER=demo
CLOCKWORK_DEMO_DEPLOY=1You get a persona picker — customer, partner, and internal-staff journeys — each landing in the surfaces that persona is entitled to see.
Demo mode is deliberately hard to switch on by accident: it requires the
explicit opt-in and the demo adapter, and it refuses to activate if any
production marker is present in the environment, flag or no flag. A demo deploy
can additionally be password-gated with CLOCKWORK_DEMO_ACCESS_PASSWORD.
The hosted demo at https://clockwork-commerce-demo.netlify.app deploys directly
from a local checkout, not from GitHub. Commit, push, and merge to main first,
then deploy from main with the Netlify CLI: merging alone changes nothing on
the live site, and deploying from anything other than main puts code in front
of prospects that exists in no commit. The route and command are in
the demo deployment runbook.
Everything is a workspace-root pnpm script; Turborepo fans them out.
| Command | What it does |
|---|---|
pnpm dev |
Run every workspace dev task in parallel |
pnpm build |
Production build of all workspaces |
pnpm typecheck |
Strict TypeScript across the monorepo |
pnpm lint |
ESLint with --max-warnings=0 (runs Next.js typegen first) |
pnpm format / :check |
Prettier write / verify |
pnpm boundaries |
dependency-cruiser module-boundary enforcement |
pnpm generate |
Regenerate openapi.json, schema.d.ts, and the typed API client |
pnpm check:generated |
Fail if generated artifacts drift from the routes |
pnpm check:traceability |
Validate docs/traceability/launch-requirements.json against its schema |
pnpm check:citation-liveness |
Require every path#symbol ledger citation to have a syntax-resolved non-test implementation use |
pnpm check:launch |
Both ledger checks above plus their tests; run before a commerce launch, not per pull request |
pnpm scan:secrets |
secretlint over the whole tree |
pnpm audit:dependencies |
pnpm audit --prod --audit-level=high |
pnpm test:unit |
Unit suites plus the release-script tests |
pnpm test:integration |
Integration suites (need the local database) |
pnpm db:test |
pgTAP tests against the rebuilt database |
pnpm test:storybook |
Storybook component tests |
pnpm test:e2e |
Playwright end-to-end, after pnpm playwright:install (test:e2e:smoke for the smoke subset) |
pnpm release:parallel |
The full release suite orchestrator (:serial, :plan, :debug) |
Choose checks by the behavior affected, not by the size of the repository. For routine edits, use the smallest set that establishes the change works:
| Change | Local validation |
|---|---|
| Documentation only | Review the diff, referenced paths and formatting; no application tests or manual deployment. |
| Copy or visual layout | Inspect the affected screen or rendered pages; run relevant existing tests where applicable. |
| A field value, default or validation rule | Run focused regression tests and the affected package's typecheck; inspect the resulting output. |
| Provider payloads or signing field geometry | Run adapter/document tests and targeted provider test-mode extraction or placement checks. |
| Authorization, persistence, workflow transitions or shared contracts | Run the relevant access, database and integration suites; expand to affected consumers. |
| Cross-cutting architecture or release infrastructure | Use the applicable broader release gates. |
Run these checks once after the final edit. A later change, failed check or specific unresolved risk justifies another run; elapsed waiting time does not. Use existing coverage where possible. Add tests for meaningful behavior and regressions, not to mirror a reversible wording or styling change. Live provider qualification is needed when the provider interaction changes or an unresolved provider issue requires it; a cosmetic change alone does not justify repeating an end-to-end signing flow. See the MNDA validation guidance.
Do not run the entire release suite locally merely to duplicate CI. The
pre-commit hooks still run. GitHub has no required checks: the Deploy workflow
deploys only after a green CI run on a push to main. A change confined to
documentation starts no CI run, so it deploys nothing. Docs that code or tests
read, such as docs/operations and docs/security, still run CI; the path
filter in .github/workflows/ci.yml lists them. Let normal automation run
without adding manual deployments or duplicate qualification. Report CI and
deployment waiting separately from hands-on work.
Four gates compose the full check, and each one is runnable on its own:
pnpm verify:static # typecheck, format, lint, boundaries, secrets, audit,
# generated drift
pnpm verify:database # db reset, pgTAP, unit + integration tests
pnpm verify:build # application and Storybook builds
pnpm verify:ui # Storybook component tests and Playwright e2e
pnpm verify # all four, in orderCI runs the same work as nine parallel shards — static, lint, unit,
integration, build, ui-1, ui-2, demo, and proof — on every pull
request and push to main that changes more than documentation. The shard list
is the same list the orchestrator uses, RELEASE_SUITE_NAMES, and a test in
scripts/release-artifacts.test.mjs fails if the workflow drifts from it. The
integration shard replays the populated database upgrade only when a pull
request changes supabase/, and on every push to main; pgTAP runs every time.
Testing uses fixed clocks, stable demo identifiers and .test domains, with
provider fakes for replay and failure scenarios.
-
Shared types live in
@clockwork/contracts— Zod schemas, roles, permissions, RFC 9457 problem details, event envelopes, pagination, IDs, idempotency keys, and provider ports. Money, clocks, authorization, and state transitions live in@clockwork/domainand stay pure. -
Generated artifacts are committed with the change that caused them. Run
pnpm generatealongside any route change;pnpm check:generatedenforces it. -
supabase/migrationsis the source of truth for schema. The Drizzle model mirrors it, andpnpm check:schema-driftfails when the two disagree. No schema change is authored in the Supabase dashboard. -
Interface text is a message in
apps/web/src/i18n/messages/. Customer and partner routes are localized, and their modules name all eight languages withdefineMessages. Staff routes render in English, and staff-only modules (operations*.ts,admin-*.ts) are English only, withdefineStaffMessages. See interface languages. -
Errors are RFC 9457
application/problem+jsonwith a request ID, a stable machine code, and a safe user message. Every list endpoint takes a stable cursor and an explicit account scope; mutations return the aggregate version and any workflow handle. -
Git hooks are installed by lefthook on
pnpm install(prepare). The pre-commit hook checks only staged files:- Prettier and secretlint read the staged content, the version the commit
records (
scripts/check-staged.sh). - ESLint (
--max-warnings=0, withnext typegenfirst for web files) reads the working-tree copy, because type-aware rules need the project on disk. If a file is partially staged, CI's full lint is the check on what was committed.
The hooks run lefthook through
scripts/lefthook.sh, which passes--no-stage-fixedso lefthook never moves unstaged changes into the git stash that all worktrees share. A branch gets this behaviour once it carries thislefthook.yml. A worktree on an older branch, which has noscripts/lefthook.sh, falls back to plain lefthook, and committing there reinstalls that branch's hook, which stashes. If you setLEFTHOOK_BIN, point it atscripts/lefthook.sh, because the hook runsLEFTHOOK_BINin place of the wrapper.LEFTHOOK=0 git commit ...skips the hooks.Run the repo-wide checks with
pnpm format:check,pnpm scan:secretsandpnpm lint, or all static gates withpnpm verify:static; CI runs them on every pull request. Whole-repo lint needs more than Node's default 4 GiB heap: useNODE_OPTIONS=--max-old-space-size=6144, matching CI. - Prettier and secretlint read the staged content, the version the commit
records (
Use the commercial readiness register, launch checklist, external gates, and current CI results to assess readiness. Passing local tests does not demonstrate a live Fil One, payment, tax or accounting cutover. Historical reports describe only their recorded commits.