Documentation & API Contracts

1. Purpose

This document describes how the Foodtruck Order repository documents itself, which tools are used and why, what counts as a public API that must be documented, and how the generated documentation is produced, checked, and published.

The documentation system has four layers:

Markdown documentation   -> human-written project/architecture/development docs (docs/)
OpenAPI                  -> the authoritative contract for the HTTP API (docs/api/openapi.yaml)
Swagger UI               -> an interactive rendering of the OpenAPI document
TypeDoc                  -> generated TypeScript reference for intentional public APIs

A single command rebuilds the whole site from a clean checkout. Blocking checks detect invalid contracts, missing configured public API comments, and generation errors. Review checks comment meaning and documentation accuracy; the operational merge policy requires passing CI.

2. Conceptual separation

The layers must not be confused:

3. Tools and why

Explicitly not used: Doxygen, Sphinx, and any tool that would parse TypeScript with regexes.

4. What is a public API

For the purposes of this repository, the documented public API is the set of symbols exported from the TypeDoc entry points configured in typedoc.json. The entry points intentionally cover one boundary per application area:

Everything else is implementation detail, even if it is technically exported:

When a new package or a new intentional cross-package boundary appears, its entry point is added to typedoc.json and its public symbols become subject to the documentation gate.

5. Which symbols require inline documentation

Every symbol exported from a documented entry point requires a meaningful inline TSDoc (/** ... */) comment:

The comment must explain semantics, not repeat the name. Prefer useful @param, @returns, and @throws tags where they convey information that the types do not; do not add tags mechanically. A placeholder comment such as /** Creates an order. */ for a function that has non-obvious behavior is a violation.

No documentation is required for private symbols, local functions/variables, implementation details, generated code, test-only code, configuration files, generated declaration files, or third-party dependencies. Missing documentation must be fixed by writing real documentation, never by weakening the gate or adding placeholder text.

6. Documentation checks

The canonical commands:

pnpm docs:check            # OpenAPI validation + TypeDoc documentation-coverage gate
pnpm docs:openapi:check    # redocly lint docs/api/openapi.yaml (redocly.yaml)
pnpm docs:typedoc:check    # TypeDoc generation with strict validation (same as pnpm docs:typedoc)
pnpm docs:links:check      # tracked Markdown files, images, targets, and heading fragments
pnpm spellcheck            # scoped authored Markdown, code comments, and UI resources

pnpm docs:check is part of the repository-wide static quality gate (pnpm quality), so every CI quality run and every local pnpm quality executes it.

All checks are blocking quality gates, not informational reports. The local-link checker lexes Markdown with marked, so fenced examples are not mistaken for links. Relative targets resolve from their source file and heading fragments use the same Unicode/duplicate-heading algorithm as the site generator. External HTTP availability is deliberately excluded from offline quality and runs weekly/manually as pnpm docs:links:external; provider or network failure remains visible there. CSpell uses pinned English, Swedish, and Hungarian dictionaries plus a small project vocabulary. Generated/vendor output, the lockfile, environment files, and credential previews are excluded; authored specifications are not broadly ignored.

The external checker is committed repository code using the workflow's pinned Node 24 built-in HTTP client, rather than an unpinned downloaded CLI. It has one narrow URL-family exemption: this private repository's four GitHub Actions workflow page/badge links return an unauthenticated 404 even when valid. Their workflow files are checked locally and their hosted conclusions remain part of CI evidence; no other GitHub or HTTP target is exempt.

7. Generating the documentation site

pnpm docs:build            # clean + Swagger UI + TypeDoc + Markdown site (complete static site)
pnpm docs:api              # Swagger UI page (docs-site/api/)
pnpm docs:typedoc          # TypeDoc reference (docs-site/reference/)
pnpm docs:site             # Markdown pages + landing page (docs-site/, docs-site/project/, ...)
pnpm docs:site:check       # required outputs plus generated local link/fragment validation
pnpm docs:serve            # serve docs-site/ locally at http://127.0.0.1:4175

pnpm docs:build is deterministic and works from a clean checkout: it starts by deleting docs-site/, then regenerates the Swagger UI page, the TypeDoc reference, and the Markdown pages. The generated site is never committed (docs-site/ is gitignored); CI and the publishing workflow always regenerate it.

After generation, docs:site:check requires non-empty landing, project, API, TypeDoc reference, raw README, and AZ-013 source outputs, then checks every generated local href/src and fragment. Raw .md fragments are validated against Markdown headings rather than treated as HTML element IDs.

The resulting docs-site/ contains:

docs-site/
  index.html            # landing page linking all three sections
  project/              # rendered Markdown documentation
  decisions/            # optional, created when docs/decisions/ contains Markdown records
  raw/                  # raw Markdown sources (so .md links keep working)
  api/                  # Swagger UI + openapi.yaml
  reference/            # TypeDoc TypeScript reference

docs-site/project/ renders the project guides, the epic specifications and their task specifications in docs/specs/, and the records in docs/decisions/; the rendered page list, sidebar groups, and slugs are defined by the projectPages manifest in scripts/docs-site-manifest.mjs. Each entry declares one committed source file (relative to the repository root), the generated slug, the sidebar title that mirrors the document's own H1, and the sidebar group. scripts/build-docs-site.mjs owns only reading those sources and writing the site tree, while scripts/docs-site-render.mjs owns Markdown rendering, .md link rewriting, and the shared HTML template; both consume the manifest, so a single declarative list drives generation and the documentation checks.

To add a new canonical guide or specification index, add exactly one manifest entry and commit the Markdown it points at — no generator code changes and no URL changes to existing pages are needed. Individual task records are intentionally not listed: as with Epic 003, an epic renders only its index and leaves its per-task Markdown as raw-linked copies under docs-site/raw/, so their stable relative .md links keep resolving without touching the manifest. Markdown that is not rendered — for example the forwarding pointer docs/specs/definition-of-done.md — is still copied to raw/, so relative .md links keep resolving to a committed source file.

Standalone requirements live in docs/specs/standalone/, with one NNN.md file per stable STANDALONE-NNN requirement and a README index. Their detailed Given/When/Then scenarios use IDs such as STANDALONE-001-S01; implementation tests carry those exact IDs and the requirement's implementation record links the evidence and validation. The standalone index is the canonical human entry point, so it is included once in the documentation-site manifest; individual requirement files remain raw-linked copies, following the same index/detail pattern as epics.

8. How to update the API contract

OpenAPI is a source document, not generated output. When a backend HTTP endpoint changes:

  1. Update docs/api/openapi.yaml in the same change that changes the route: path, method, parameters, request body, response status codes, response schemas, headers, and error responses. Reuse/update the shared components instead of duplicating definitions.
  2. Run pnpm docs:openapi:check (and, via the backend unit suite, pnpm test) to validate the document.
  3. The automated contract test (backend/src/openapi-contract.test.ts) checks that the documentation and the implementation agree on the route surface: every Fastify route (excluding the implicit Fastify HEAD route) must have a matching documented path and operation, and every documented path and operation must exist as a Fastify route.
  4. Bump info.version in the OpenAPI document for breaking contract changes.

The contract test intentionally verifies the route surface, not the implementation; do not duplicate the route table elsewhere.

9. Cloudflare Pages publishing

Documentation is published to Cloudflare Pages by the dedicated workflow .github/workflows/cloudflare-pages.yml, which runs on pushes to main (and on manual workflow_dispatch):

  1. Check out the repository.
  2. Install dependencies with the repository's pinned pnpm/Node strategy (pnpm install --frozen-lockfile).
  3. Run the required quality checks: pnpm quality (which includes docs:check — OpenAPI validation and the documentation-coverage gate) and pnpm test.
  4. Build the complete site with pnpm docs:build.
  5. Verify required output and generated links with pnpm docs:site:check.
  6. Upload docs-site/ to the already-existing Cloudflare Pages project foodtruck-order-docs (Direct Upload) with wrangler, run via pnpm dlx with the version pinned in the workflow (pnpm dlx wrangler@4.131.1 pages deploy docs-site --project-name=foodtruck-order-docs --branch=main). No generated HTML is committed to the repository.

Authentication uses exactly the two repository secrets CLOUDFLARE_API_TOKEN (a least-privilege token created for Pages deployment) and CLOUDFLARE_ACCOUNT_ID; neither value is hard-coded anywhere in the repository, and the Cloudflare Global API Key is not used.

The division of responsibility is deliberate: GitHub hosts the source, runs the CI quality gates, and generates the site; Cloudflare Pages only hosts the generated static output. Cloudflare is not connected to GitHub (no Git integration, no Cloudflare-side build) and cannot clone the private repository — the direct upload is the only path between the two. The published URL is the Pages project's production domain (Cloudflare dashboard, Workers & Pages → foodtruck-order-docs); --branch=main matches the workflow trigger and the project's production branch (the default for Direct Upload projects), so automated deployments are production deployments.

Wrangler uploads the rebuilt site directly; it creates no GitHub Pages artifact and requires no stale-artifact cleanup on workflow reruns. The workflow's only permission is contents: read; nothing is uploaded when any step fails.

The normal CI pipeline (.github/workflows/ci.yml) already runs the same quality gates on every pull request, and its docs job builds the site (pnpm docs:build), so broken TypeDoc/Swagger/site generation fails PR validation. The operational merge policy requires passing CI; publishing independently checks its own gates.

10. Deliberately excluded cases

11. Source ownership and maintenance

Keep one authoritative home for each subject:

Subject Authoritative location Maintenance rule
Architecture and dependency boundaries architecture.md Explain responsibilities and rationale; link to commands elsewhere.
Technology choices and versions techstack.md Record adopted/deferred technology, not feature status.
Setup and developer commands development.md Keep runnable workflows here; link to policy and architecture.
Test levels, data, and coverage test-strategy.md Own evidence selection and suite composition.
CI jobs, artifacts, and enforcement ci.md Describe pipeline mechanics and enforcement gaps.
Containers and Compose docker.md Own image, service, startup, and isolation behavior.
Documentation and API/reference generation documentation.md Own placement, contract, generation, and publishing rules.
Completion requirements definition-of-done.md State what must be evidenced; do not duplicate implementation mechanics.
Feature requirements and traceability specs/ Preserve committed records and stable identifiers as history.
Architectural decisions decisions/ Add or supersede records; never rewrite decision history.

Cross-link instead of copying full commands or policies into multiple guides. A root guide should start with its purpose and document map before repository-specific detail. Reorder a living guide when incremental additions obscure that structure, but do not restructure documentation solely for visual uniformity.

current-state.md is the current implementation/validation/remaining-work snapshot. ai-handover.md contains only the latest task and continuation notes, linking to that snapshot. Keep neither as an accumulating changelog; historical execution records remain in Git. Informal personal notes are not maintained project documentation; intentional product ideas belong in the appropriate specification/backlog, while discarded notes remain recoverable from Git history.

Markdown targets 100 columns with Prettier proseWrap: always; see development §13. Long unbreakable URLs, code, and small tables are exceptions. Prefer sections/lists to wide prose tables.