Skip to content

Latest commit

 

History

History
263 lines (218 loc) · 17.4 KB

File metadata and controls

263 lines (218 loc) · 17.4 KB

Contributing to Clockwork

This guide preserves the engineering setup, commands and conventions formerly in the root README. Start with the README for product context and the demo.

Architecture

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.

Repository layout

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.

Quick start

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:3000

pnpm 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

Environment

.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.

Guided demo (no credentials required)

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=1

You 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.

Commands

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)

Verification and CI

Proportionate validation

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.

Full verification commands

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 order

CI 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.

Conventions that matter

  • 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/domain and stay pure.

  • Generated artifacts are committed with the change that caused them. Run pnpm generate alongside any route change; pnpm check:generated enforces it.

  • supabase/migrations is the source of truth for schema. The Drizzle model mirrors it, and pnpm check:schema-drift fails 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 with defineMessages. Staff routes render in English, and staff-only modules (operations*.ts, admin-*.ts) are English only, with defineStaffMessages. See interface languages.

  • Errors are RFC 9457 application/problem+json with 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, with next typegen first 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-fixed so lefthook never moves unstaged changes into the git stash that all worktrees share. A branch gets this behaviour once it carries this lefthook.yml. A worktree on an older branch, which has no scripts/lefthook.sh, falls back to plain lefthook, and committing there reinstalls that branch's hook, which stashes. If you set LEFTHOOK_BIN, point it at scripts/lefthook.sh, because the hook runs LEFTHOOK_BIN in place of the wrapper. LEFTHOOK=0 git commit ... skips the hooks.

    Run the repo-wide checks with pnpm format:check, pnpm scan:secrets and pnpm lint, or all static gates with pnpm verify:static; CI runs them on every pull request. Whole-repo lint needs more than Node's default 4 GiB heap: use NODE_OPTIONS=--max-old-space-size=6144, matching CI.

Release evidence

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.