Skip to content

ADR-002: System Documentation Layout (Centralized)

Status

Accepted

Context

ADR-001 established docs/ as the canonical documentation root and defined five categories: conventions/, commands/, security/, decisions/, and development/. Those categories cover rules and durable trade-offs but not descriptive documentation of the running system: bounded contexts, state machines, domain events, cross-context flows, app responsibilities, and external integrations.

The project's complexity is concentrated in that second group. Three major state machines (ShiftStatus with 12 states, TransferStatus with 8, TransactionStatus with 6), 65+ domain events spanning 15 bounded contexts, and 10 deployable apps. Until now this knowledge lived in code, in a partially populated VitePress site at apps/docs/, and in scattered notes inside a legacy lib-docs/ tree.

Two consumer types need this documentation:

  • Humans: new contributors, support engineers, future partners.
  • Agents: the coding agent needs to understand state transitions before touching code; future agents may execute operational actions on the user's behalf and need predictable progressive disclosure.

An earlier draft of this ADR placed per-context documentation in packages/<ctx>/docs/ alongside the source code. After implementing and reviewing the draft, two problems surfaced:

  1. Noise in packages/: mixing docs/ next to src/ made the package tree harder to scan.
  2. Discoverability: reading the system end-to-end required jumping between many package directories.

This ADR replaces that approach with a centralized layout.

Decision

1. New Canonical Category: docs/system/

Add docs/system/ as the sixth canonical branch of the docs/ tree. It holds everything descriptive about the running system. packages/ and individual apps/<app>/ directories are not used as documentation storage — they stay pure code.

2. Tree Shape

docs/system/
├── readme.md              # global index and progressive-disclosure entry point
├── contexts/              # one subfolder per bounded context
│   ├── readme.md          # catalog
│   ├── <ctx>/
│   │   ├── readme.md      # overview
│   │   └── events.md      # events emitted and consumed
├── state-machines/        # TRANSVERSAL section
│   ├── readme.md          # catalog with owner-context column
│   └── <status>.md        # one file per status enum, system-wide
├── apps/                  # one subfolder per deployable
│   ├── readme.md          # catalog
│   └── <app>/readme.md
├── architecture/          # C4 diagrams (context, containers, components, code)
├── events/                # global event catalog
│   ├── readme.md
│   └── flows/             # cross-context sagas
├── integrations/          # CEXes, blockchains, providers
├── runbooks/              # operational scenarios and setup guides
├── glossary.md
└── _templates/            # markdown templates for new system docs

3. State Machines Are Transversal

State machines live at docs/system/state-machines/<slug>.md, not under each context. This lets a reader see every state machine in the system at a glance and removes an extra directory level from the path. Each state-machine file names its owner context in the header.

4. Apps Are Subfolders

Each app gets a folder (docs/system/apps/<app>/) with readme.md plus any app-specific sibling files (endpoint catalogs, job catalogs, etc.). This scales better than one flat file per app once apps grow their own auxiliary docs.

5. Predictable Paths

DocumentPath
Context overviewdocs/system/contexts/<ctx>/readme.md
Events for a contextdocs/system/contexts/<ctx>/events.md
State machinedocs/system/state-machines/<slug>.md
App overviewdocs/system/apps/<app>/readme.md
Cross-context flowdocs/system/events/flows/<slug>.md
Runbookdocs/system/runbooks/<slug>.md
External integrationdocs/system/integrations/<kind>/<name>.md

Slugs are always kebab-case of the enum, entity, or app name as it appears in TypeScript. An agent can infer the path from the name alone.

6. VitePress Is the Renderer

The VitePress site under apps/docs/ becomes a pure renderer. Its srcDir points at ../../docs, and it consumes the canonical markdown directly. No symlinks, no rewrites, no duplicated files. Any app-only VitePress artifacts (landing page hero, introduction pages) move into docs/index.md and docs/introduction/.

7. Five Document Templates

docs/system/_templates/ holds the five reusable templates for new system docs:

  • bounded-context.md — overview of a context.
  • state-machine.md — one status enum with transitions, invariants, and code pointers.
  • event-flow.md — one cross-context saga.
  • app-overview.md — one deployable.
  • external-integration.md — one CEX, blockchain, or provider.

8. Extend the create-doc Skill

The create-doc skill gains a sixth category, system, with routing rules that map a topic (context, state machine, app, flow, runbook, integration) to the correct path and template.

9. Retire lib-docs/

lib-docs/ is deleted. Rescued documents move to their canonical homes inside docs/system/contexts/<ctx>/ or docs/system/apps/<app>/.

Rationale

Why Centralize Instead of Per-Package

Per-package documentation puts markdown next to the code it describes, which sounds like it should help agents working inside a single package. In practice it creates two problems: the package tree gets noisier, and reading the system end-to-end requires jumping between many directories. Centralization trades a small loss of code-locality for a large gain in discoverability — for both humans and agents — and keeps packages/ readable as a code surface.

Why Transversal State Machines

The 3 major state machines (ShiftStatus, TransferStatus, TransactionStatus) are the densest documents in the system and the ones most frequently consulted together. Keeping them under a single directory lets a reader (or agent) scan every machine at once. The owner context is named in each file's header, and the transversal index at docs/system/state-machines/readme.md groups them by owner.

Why VitePress as a Pure Renderer

Pointing srcDir at docs/ eliminates two sources of drift: no duplicated markdown and no symlinks to keep in sync across filesystems. The renderer becomes swappable — if VitePress is ever replaced, only apps/docs/.vitepress/config.js changes.

Why a Sixth Category

Forcing system description into development/ or conventions/ would muddle the semantics ADR-001 deliberately separated. development/ is for in-flight proposals; conventions/ is for rules. System description is neither — it is a moving target that tracks code.

Consequences

  • packages/ stays pure code. Every descriptive markdown file is reachable from docs/system/readme.md.
  • The VitePress site consumes docs/ directly via srcDir. No symlinks, no rewrites.
  • docs/system/ starts with empty readmes for 15 contexts and 9 apps. The cold start is accepted: pages fill in as contributors touch the corresponding context or app.
  • The create-doc skill grows a category and five templates.
  • lib-docs/ is retired; any future reference to it is invalid.

Deferred Scope

  • Automatic generation of state-machine diagrams from TypeScript enum definitions.
  • Synchronization tooling between code (events, state transitions) and documentation.
  • Link-checker or dead-link CI step.

Relation To Existing ADRs

  • Extends ADR-001 by adding the sixth canonical category (docs/system/) and formalizing the centralized layout.