13 — Quality & Release Policy
13 — Quality & Release Policy
Section titled “13 — Quality & Release Policy”Status: Design
Date: 2026-05-23
Depends on: master design, all 01–12 sub-specs
Research: research/quality-and-testing-best-practices.md
1. Purpose & scope
Section titled “1. Purpose & scope”Owns: every gate between “agent wrote some code” and “production accepts it.” Defines test coverage policy, lint/type-check stance, formatting, pre-commit hooks, GitHub Actions CI matrix, branch protection, release process, and per-sub-system skill recipes that implementation agent teams MUST use.
NOT in scope: business logic itself (each sub-system owns its tests); legal sign-off (12); platform pricing (01).
This spec is load-bearing for main branch protection — every other sub-spec ships through these gates.
2. Decision summary
Section titled “2. Decision summary”| # | Decision |
|---|---|
| D31 | Test runner: Vitest + @cloudflare/vitest-pool-workers 0.16.x. Single test toolchain across Workers, DOs, Workflows, Astro. |
| D32 | Coverage policy: 100% lines/branches/functions/statements via @vitest/coverage-v8. Exemptions only via /* v8 ignore */ with an inline reason. |
| D33 | Lint stack: Biome 2.x for format + style; typescript-eslint (type-aware) for semantics; eslint --max-warnings 0. Pre-commit via husky + lint-staged. |
| D34 | TypeScript strictness: strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes, noImplicitOverride, noPropertyAccessFromIndexSignature, useDefineForClassFields, verbatimModuleSyntax. |
| D35 | Branch protection: main requires 7 status checks + linear history + dismiss-stale-reviews. 1 reviewer default; 2 for 04, 05, 12 paths. |
| D36 | Release flow: Conventional Commits → release-please for changelog + tag → GitHub Environments-gated production deploy with required reviewer. |
| D37 | Status badges: CI, coverage %, last deploy, license, types-strict, security-scan — visible in README + docs-site landing. |
3. Testing strategy by layer
Section titled “3. Testing strategy by layer”3.1 Worker handlers
Section titled “3.1 Worker handlers”- Vitest +
@cloudflare/vitest-pool-workers(cloudflare:test). SELF.fetch(...)for full handler round-trips against the actualwrangler.tomlbinding graph.- Per-worker
vitest.config.tsextending a sharedvitest.base.tsinpackages/config. - Hono workers (per ADR-0001 / D40) additionally expose
app.fetch(request, env, ctx)for in-process round-trips that bypass the workerd boot cycle — used for fast unit-of-routing tests.SELF.fetchremains the canonical end-to-end gate.
3.2 Durable Objects
Section titled “3.2 Durable Objects”runInDurableObject(stub, async (instance, state) => { ... })for direct invariant assertions on DO instance state.runDurableObjectAlarm(stub)to fire alarm handlers deterministically — used heavily by04-auction-engineclose logic.- WebSocket lifecycle covered via
state.acceptWebSocket/webSocketMessageinvoked underrunInDurableObject(used by06-real-time-hub).
3.3 Cloudflare Workflows
Section titled “3.3 Cloudflare Workflows”introspectWorkflowInstance(vitest-pool-workers ≥ 0.9.0) — first-class Workflow testing.- Fake clock via
vi.useFakeTimers()forstep.sleepUntilranges. instance.sendEventtestable in-process;waitForEventresolves against the fake clock.- Used by
05-payment-sagafor all 15 vertices + every compensation branch.
3.4 Stripe interactions
Section titled “3.4 Stripe interactions”- stripe-mock (Stripe’s own OpenAPI mock server) over a local port, started by
globalSetupper workspace. - Custom narrow stubs only for invoice-payment edge cases that stripe-mock doesn’t simulate (e.g.
requires_action→ success transition). - Idempotency-key reuse assertions enforced.
3.5 Astro pages and islands
Section titled “3.5 Astro pages and islands”- Vitest + happy-dom + Astro Container API (
experimental_AstroContainer). renderToStringfor SSR snapshots;@testing-library/domqueries for island behavior.apps/webruns both jsdom-style island tests ANDcloudflare:test-pool tests for endpoint handlers.
3.6 D1, KV, R2, Queues
Section titled “3.6 D1, KV, R2, Queues”- Miniflare in-memory implementations via
cloudflare:test. - D1: pair with
drizzle-kitgenerated migrations applied to the test DB inbeforeAll. - Queues: producer tests assert messages enqueued; consumer tests inject batches via
cloudflare:testhelpers.
3.7 Test layout per app/package
Section titled “3.7 Test layout per app/package”apps/<app>/ src/ test/ unit/ *.unit.test.ts (vitest jsdom or happy-dom) integration/ *.int.test.ts (vitest-pool-workers, SELF.fetch) do/ *.do.test.ts (runInDurableObject) workflow/ *.wf.test.ts (introspectWorkflowInstance) vitest.config.tspackages/<pkg>/ src/ test/ *.test.ts4. Coverage policy
Section titled “4. Coverage policy”- Vitest config:
coverage: { provider: 'v8', reporter: ['text', 'lcov', 'json-summary'], thresholds: { lines: 100, functions: 100, branches: 100, statements: 100 }, all: true }. - Coverage failure breaks CI.
- Exemptions live behind
/* v8 ignore next */or/* v8 ignore start */ ... /* v8 ignore end */with an inline reason comment on the same/preceding line. Examples allowed:- Branded-type assertion fail-fast for truly-impossible states.
- Generated D1 migration SQL imports.
- Third-party ambient types or shim files.
- A repo-wide
pnpm run coverage:auditscript (inpackages/config/scripts) enumerates everyv8 ignoredirective and posts the list as a CI comment for review.
5. TypeScript & lint policy
Section titled “5. TypeScript & lint policy”5.1 tsconfig.base.json
Section titled “5.1 tsconfig.base.json”{ "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "Bundler", "strict": true, "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "useDefineForClassFields": true, "verbatimModuleSyntax": true, "noEmit": true, "skipLibCheck": false, "lib": ["ES2022", "WebWorker"] }}Per-app tsconfig.json extends this. apps/web/tsconfig.json adds "lib": ["ES2022", "DOM", "DOM.Iterable", "WebWorker"].
5.2 Lint
Section titled “5.2 Lint”biome.jsonat root configures format + import-sort + a curated style ruleset.eslint.config.js(flat config) loads@typescript-eslintwithparserOptions.project: truefor type-aware rules +astro-eslint-pluginfor.astrofiles.- CI runs both in parallel (
biome check . --error-on-warningsandeslint . --max-warnings 0). - Format check via
biome format --check .; no separate Prettier step.
5.3 Pre-commit (husky + lint-staged)
Section titled “5.3 Pre-commit (husky + lint-staged)”"lint-staged": { "*.{ts,tsx,astro}": ["biome check --apply", "eslint --max-warnings 0 --no-warn-ignored"], "*.{json,md,yaml}": ["biome format --write"]}- Run on
git commit.--applyauto-fixes safe issues; if lint-staged exits non-zero, commit is blocked. - A
pre-pushhook runspnpm typecheckto catch type errors before they hit CI.
6. GitHub Actions matrix
Section titled “6. GitHub Actions matrix”Seven workflows under .github/workflows/. All workflows use the same node + pnpm setup steps from a shared composite action (.github/actions/setup).
| File | Triggers | Required for main? |
|---|---|---|
ci.yml | push, pull_request | YES |
preview.yml | pull_request | YES (preview deploys must build) |
deploy.yml | push to main | n/a (it IS the prod gate) |
security-scan.yml | push, pull_request, weekly schedule | YES |
lighthouse-ci.yml | pull_request (apps/web) | YES (perf budget pass) |
coverage-comment.yml | pull_request | NO (informational PR comment) |
release.yml | push to main | n/a (uses release-please) |
compiler-pilot.yml | affected pull requests, workflow_dispatch | NO (isolated native compiler comparison; existing tsc remains authoritative) |
6.1 ci.yml — the canonical gate
Section titled “6.1 ci.yml — the canonical gate”Matrix per workspace (web, api, admin, ws, queue, cron, webhooks, docs-site + each packages/*):
pnpm install --frozen-lockfilepnpm run lint→ biome + eslintpnpm run typecheck→tsc --noEmit -p .pnpm run test -- --coverage→ vitest with 100% thresholdspnpm run build→ wrangler/astro build artifacts- Upload coverage JSON to artifact (consumed by
coverage-comment.yml)
Jobs run with concurrency: ${{ github.ref }} to cancel stale runs.
6.2 security-scan.yml
Section titled “6.2 security-scan.yml”pnpm audit --audit-level=moderategitleaks detect- CodeQL (default JS/TS query packs + a custom query for
eval/Functionin worker code) headers-check.ymlsmoke test against deployed preview URLs
6.3 lighthouse-ci.yml
Section titled “6.3 lighthouse-ci.yml”Budget targets per lighthouserc.json: performance ≥ 95, accessibility ≥ 95, best-practices ≥ 95, SEO ≥ 95. Runs against the deployed preview URL.
6.4 coverage-comment.yml
Section titled “6.4 coverage-comment.yml”Uses davelosert/vitest-coverage-report-action to post a per-PR coverage delta + a heatmap. Fails the action if any file in the diff is below 100%.
7. Branch protection — required checks
Section titled “7. Branch protection — required checks”main:
- Linear history enforced (squash merges only).
- Dismiss stale approvals on push.
- Required reviews: 1 (default).
- 2 reviewers required when PR touches:
apps/api/**/auction-*,apps/api/**/saga-*,apps/api/**/payment-*,packages/saga/**,packages/auction-engine/**,packages/legal/**,infra/terraform/**— enforced viaCODEOWNERS. - Required status checks:
ci / lintci / typecheckci / test(covers coverage threshold)ci / buildsecurity-scan / auditsecurity-scan / codeqllighthouse-ci / perf-budget(only on PRs touchingapps/web)
- Force-push disabled. Branch deletion disabled.
8. Release process (“locking the release”)
Section titled “8. Release process (“locking the release”)”8.1 Versioning
Section titled “8.1 Versioning”- Conventional Commits (
feat:,fix:,chore:,docs:,refactor:,test:,perf:,build:,ci:,style:,revert:, plusfeat!:/BREAKING CHANGE:). release-pleaseruns on every push tomain. It opens (or updates) a single “release PR” with CHANGELOG entries derived from the new commits.
8.2 Production deploy gate
Section titled “8.2 Production deploy gate”deploy.ymlruns on every merge tomain, but the prod deploy step (wrangler deploy --env production) is wrapped in a GitHub Environment (production) configured with:- Required reviewer (you / founder + admin role from
10). - Wait timer of 5 minutes for human cancel.
- Required reviewer (you / founder + admin role from
- A deploy that fails health-check rolls back via
wrangler rollbackand posts a Sentry release-noted error.
8.3 Tag immutability
Section titled “8.3 Tag immutability”- Tags created by release-please are protected (rule under “tag protection rules”). Re-pushing the same tag is rejected.
9. Status badges (README + docs-site landing)
Section titled “9. Status badges (README + docs-site landing)”Embed shields.io badges, ordered:
[](.)[](.)[](.)[](.)[](.)[](LICENSE)[](CODE_OF_CONDUCT.md)Coverage badge is served by a tiny endpoint Worker at nowyourlink.com/badges/coverage.json produced by ci.yml’s coverage step.
10. Specialized agent + skill recipes
Section titled “10. Specialized agent + skill recipes”For implementation, consult the row for the subsystem being changed. Load only the skills and references needed for that task. Skill and agent tool names vary by client; the review and testing outcomes below apply across clients.
10.1 Workflows (per task type)
Section titled “10.1 Workflows (per task type)”The names below identify the preferred workflows when installed. A missing plugin is not a reason to skip its testing or review outcome: use available tools for the applicable checks and report the substitution. Never claim an unavailable tool or independent reviewer ran. Request external reviewer messages only within the user’s authorization. Local implementation and validation can continue without repeatedly asking for approval.
- New feature:
workflows:tdd-cycle(red → green → refactor enforced). - Bug fix:
superpowers:systematic-debuggingfirst, thenworkflows:tdd-cycle. - PR review (before merging):
workflows:full-review(multi-agent comprehensive) +pr-review-toolkit:review-pr. - Pre-merge correctness sweep:
pr-review-toolkit:code-reviewer+pr-review-toolkit:silent-failure-hunter+pr-review-toolkit:type-design-analyzer+pr-review-toolkit:pr-test-analyzer. - Security-sensitive change:
security-review+tools:security-scan. - Real-app verification:
verifyskill (drives the actual app to confirm behavior). - Refactor:
tools:refactor-clean+tools:tdd-refactor.
10.2 Per-sub-system recipe
Section titled “10.2 Per-sub-system recipe”Coverage note. This table is populated per-sub-system as each recipe is authored, so it is intentionally incomplete — rows are added in the PR that lands (or substantially advances) the corresponding sub-system, not all at once. A spec that is not yet listed (e.g.
14 design-system,16 acquisition-seo-social) simply has no canonical recipe authored yet; until a row exists, fall back to the per-task workflows in §10.1 and the defaultpr-review-toolkit:review-prsweep. The “teams MUST consult this table” rule applies to the rows that exist — a missing row is a not-yet-authored recipe, not an exemption from §10.1.
| Spec | Primary skills | Specialized review agents |
|---|---|---|
| 01 foundation-infra | tools:deploy-checklist, tools:k8s-manifest (n/a, but adjacent: terraform-validate) | pr-review-toolkit:code-reviewer |
| 02 identity-onboarding | workflows:tdd-cycle, tools:security-scan | silent-failure-hunter, type-design-analyzer |
| 03 ad-moderation | workflows:tdd-cycle, tools:ai-review (for Workers AI integration) | code-reviewer, pr-test-analyzer |
| 04 auction-engine | workflows:tdd-cycle (strict — DO state invariants), superpowers:test-driven-development | type-design-analyzer, silent-failure-hunter |
| 05 payment-saga | workflows:tdd-cycle + tools:api-mock (Stripe), security-review | silent-failure-hunter (mandatory), code-reviewer, pr-test-analyzer |
| 06 real-time-hub | workflows:tdd-cycle, superpowers:test-driven-development | type-design-analyzer, code-reviewer |
| 07 public-site-archive | frontend-design:frontend-design, tools:accessibility-audit | code-reviewer, comment-analyzer |
| 08 advertiser-dashboard | frontend-design:frontend-design, tools:data-validation (pixel events) | silent-failure-hunter, code-reviewer |
| 09 engagement-features | frontend-design:frontend-design, tools:accessibility-audit | code-reviewer |
| 10 admin-console | frontend-design:frontend-design, workflows:security-hardening | code-reviewer, silent-failure-hunter |
| 11 docs-site | tools:doc-generate, frontend-design:frontend-design | comment-analyzer |
| 12 legal-compliance | tools:compliance-check, security-review | code-reviewer (templates), comment-analyzer |
| 13 quality-and-release-policy (this) | claude-md-management:claude-md-improver, claude-automation-recommender | code-reviewer (workflows + scripts) |
| 15 engagement-marketing | workflows:tdd-cycle (strict — sealed-bid + consent invariants), tools:compliance-check (DOI / UWG §7 / GDPR opt-in proof), security-review (token-kind confused-deputy, RFC 8058 unsub seam), superpowers:test-driven-development (verify-the-zero probes on every type-aware rule) | silent-failure-hunter (mandatory — every producer emit / ledger write / attribution flip is best-effort .catch()-swallowed), code-reviewer (sealed-bid template lint + no-amount-join schema test + legal copy), pr-test-analyzer (per-signal smuggled-amount-fails-.parse() + soft-bounce-does-not-block-3DS + capped-push-makes-zero-/push/send coverage) |
| 16 acquisition-seo-social | workflows:tdd-cycle (red → green → refactor, one PR per [16-x][Pn] issue), superpowers:test-driven-development (100% coverage, no live network — ship a mock double, D48), seo-audit (on-demand Unlighthouse/lychee/JSON-LD vs a preview — review-only, D47), social-post-compose (draft → human approval → enqueue to q-social-publish), the scheduled .github/workflows/growth-routines.yml Action (#800 — runs weekly-growth-report + seo-audit headless on a weekly cadence; its only repository-write scope is issues: write so it can only open review-artifact issues, never push/merge/publish, D47); reuse-don’t-fork apps/web/src/lib/seo.ts, apps/cron/src/scheduled.ts lanes, @nowyourlink/social-adapters, @nowyourlink/pixel + METRICS, consent #727/#739 | silent-failure-hunter (mandatory — every adapter publish / search-ingest / link-probe is a never-throw PublishResult/result with a retryable flag, plus the cron lanes’ per-item .catch() isolation), code-reviewer (no-PII / k-anon admin readouts #753, free-text-vs-CHECK enum hygiene, the human-approval gate is never bypassed), pr-test-analyzer (per-lane idempotent-re-run-is-a-no-op + D48 no-live-network + nothing-auto-publishes coverage) |
| 17 email-template-system | Recipe — “add/modify an email template”: workflows:tdd-cycle / superpowers:test-driven-development (red → write the template’s harness test calling renderAndAssertEmail(<MyEmail/>) and watch it fail; green → author the .tsx wrapping BaseLayout + primitives until the harness passes; refactor → extract copy into i18n string tables, no translated/{{var}} literals in JSX). Gates: the #808 harness (@nowyourlink/email-templates/test — asserts on the rendered {html,text}, never source JSX: <102 KB, all-CSS-inlined, table layout, alt/width/height, plaintext-has-every-CTA-URL, AA contrast, caniemail lint) and the #811 drift check (pnpm email:publish --dry-run validates the case-sensitive {{{VAR}}} contract; --check-drift is read-only/CI-safe). Preview with pnpm --filter @nowyourlink/email-templates email; sign off rendering/dark-mode in real clients per packages/email-templates/docs/manual-qa-checklist.md before release; then wire the render* fn into the call site. frontend-design:frontend-design for brand-token theme work. Full recipe: packages/email-templates/README.md. | code-reviewer (ET4 variant/footer rules — marketing carries List-Unsubscribe + preference + postal, transactional carries none — and the {{{VAR}}} casing contract), pr-test-analyzer (the harness gate and drift --dry-run actually run per template; 100% coverage), comment-analyzer |
11. Shared agent instructions
Section titled “11. Shared agent instructions”AGENTS.md is the canonical cross-client entrypoint for quality requirements;
CLAUDE.md imports it. Maintain the policy link and completion gate there rather
than duplicating the instructions in both files. The gate remains pnpm verify
(lint, typecheck, test coverage, build), zero warnings, and the coverage policy
above. Use the relevant workflow in §10 for new features and pre-merge review.
The root package.json is the source of truth for the command implementation;
do not maintain a second copy of its script here.
12. Acceptance criteria (gate for “13 done”)
Section titled “12. Acceptance criteria (gate for “13 done”)”tsconfig.base.json,biome.json,eslint.config.jschecked in at root with the settings in §5.- Shared
vitest.base.tsinpackages/configreferenced by all workspacevitest.config.ts. - All 7 GitHub Actions workflows live under
.github/workflows/. - Branch protection rules applied to
mainongithub.com/ghardenlab/nowyourlinkmatching §7 — captured ininfra/terraformviagithub_branch_protectionresource. - README contains the 7 status badges.
CODEOWNERSenforces the 2-reviewer rule on04/05/12paths.release-pleaseconfig inrelease-please-config.json+.release-please-manifest.json.pnpm verifyruns clean on the empty scaffold.- CI runs green on the initial scaffolding PR.
- Docs-site (
11) surfaces a “Quality bar” page in the Operations nav group pointing at this spec.
13. Open items
Section titled “13. Open items”- Q12 (carried from research): verify
@cloudflare/vitest-pool-workers0.16.x has resolved historical Queue consumer flakiness underisolatedStorage. If not yet fixed, document a temporary lower threshold (e.g. 95%) for queue-consumer tests with a tracking issue and forced re-evaluation at every minor bump. Otherwise hold at 100%. - Q13: Will
release-pleasefor a monorepo emit per-app changelogs (apps/web/CHANGELOG.md) or a single root one? Decide before first release.
14. Agent-team hints
Section titled “14. Agent-team hints”- Test files import from
cloudflare:test, not from individual@miniflare/*packages — saves churn when CF rev the pool worker. - Drizzle migration tests: use
migratefromdrizzle-orm/d1-migratorinbeforeAll, never raw SQL. - DO alarm tests with
runDurableObjectAlarm(stub)— setvi.setSystemTimefirst to fix the clock, then trigger. - Workflow tests: prefer
introspectWorkflowInstanceover manual mocks; only mock the underlying Stripe SDK, neverstep.doitself. pnpm verifymust remain a single command — it’s what tooling, hooks, CI, and humans run. Do not split.- New CI workflow files require a corresponding entry in this spec’s §6 table — drift is forbidden.