Skip to content

Commit f01fcf4

Browse files
hermes-agentjonathanperis
authored andcommitted
docs: close code and docs audit gaps
1 parent f55dffa commit f01fcf4

15 files changed

Lines changed: 276 additions & 19 deletions

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
[![Build Check](https://github.com/jonathanperis/rinha2-back-end-dotnet/actions/workflows/build-check.yml/badge.svg)](https://github.com/jonathanperis/rinha2-back-end-dotnet/actions/workflows/build-check.yml) [![Main Release](https://github.com/jonathanperis/rinha2-back-end-dotnet/actions/workflows/main-release.yml/badge.svg)](https://github.com/jonathanperis/rinha2-back-end-dotnet/actions/workflows/main-release.yml) [![CodeQL](https://github.com/jonathanperis/rinha2-back-end-dotnet/actions/workflows/codeql.yml/badge.svg)](https://github.com/jonathanperis/rinha2-back-end-dotnet/actions/workflows/codeql.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
66

7-
**[Live demo →](https://jonathanperis.github.io/rinha2-back-end-dotnet/)** | **[Documentation →](https://github.com/jonathanperis/rinha2-back-end-dotnet/wiki)**
7+
**[Live demo →](https://jonathanperis.github.io/rinha2-back-end-dotnet/)** | **[Documentation →](https://jonathanperis.github.io/rinha2-back-end-dotnet/docs/)**
88

99
---
1010

@@ -54,6 +54,8 @@ API available at `http://localhost:9999`
5454
| `/clientes/{id}/extrato` | GET | Get account balance statement |
5555
| `/healthz` | GET | Health check |
5656

57+
For request/response payloads, validation behavior, and known implementation notes, see the [API Reference](https://jonathanperis.github.io/rinha2-back-end-dotnet/docs/api-reference/).
58+
5759
## Project Structure
5860

5961
```
@@ -69,8 +71,8 @@ rinha2-back-end-dotnet/
6971
├── prod/docker-compose.yml — Prod stack with GHCR images
7072
├── nginx.conf — Load balancer (least_conn)
7173
├── grafana/ — Legacy Grafana dashboard provisioning
72-
├── docs/wiki/ — Markdown source for the documentation/wiki pages
73-
├── docs/ — Astro GitHub Pages site + published k6 reports
74+
├── docs/wiki/ — Markdown source rendered to the Pages docs routes
75+
├── docs/ — Astro 6.4 GitHub Pages site using Sätteri + published k6 reports
7476
└── .github/workflows/ — CI/CD pipelines
7577
```
7678

docs/README.md

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
# Docs
22

3-
Astro static site deployed to GitHub Pages. The Markdown pages under `docs/wiki/` are the source for the public documentation/wiki-style routes.
3+
Astro static site deployed to GitHub Pages. The Markdown pages under `docs/wiki/` are the source for the public documentation routes at `/docs/`.
4+
5+
## Architecture
6+
7+
| Area | Source of truth |
8+
|---|---|
9+
| Site framework | Astro `^6.4.2` |
10+
| Markdown renderer | Astro 6.4 `markdown.processor` using `@astrojs/markdown-satteri` |
11+
| Markdown content | `docs/wiki/*.md` |
12+
| Docs route renderer | `docs/src/pages/docs/[...slug].astro` |
13+
| Sidebar/category order | `docs/src/sidebar.config.ts` |
14+
| Reports archive | Static files in `docs/public/reports/*.html` rendered by `docs/src/pages/reports/index.astro` |
15+
| Production base path | `/rinha2-back-end-dotnet` when `NODE_ENV=production` |
16+
| Build output | `docs/out/` |
17+
18+
The docs are static. There is no server runtime after GitHub Pages publishes the generated HTML.
19+
20+
## Adding or editing a docs page
21+
22+
1. Add or edit Markdown in `docs/wiki/`.
23+
2. If the page is new, add the slug to `SECTION_CATEGORIES` in `docs/src/sidebar.config.ts`.
24+
3. Add a label and summary for the slug in `docs/src/pages/docs/[...slug].astro`.
25+
4. Build locally before opening a PR.
26+
27+
The docs build has a build-time assertion that every sidebar slug maps to a real `docs/wiki/{slug}.md` file.
428

529
## Commands
630

@@ -10,8 +34,27 @@ Run from this directory (`docs/`):
1034
|---|---|
1135
| `bun install` | Install dependencies |
1236
| `bun run dev` | Start dev server |
13-
| `bun run build` | Build the Astro site to `./out/` |
14-
| `bun run preview` | Preview production build locally |
37+
| `NODE_ENV=production bun run build` | Build the GitHub Pages-shaped site to `./out/` with the repository base path |
38+
| `bun run preview` | Preview the production build locally |
39+
| `bun run lint` | Run ESLint |
40+
41+
Use Bun for this package so `bun.lock` stays authoritative.
42+
43+
## Markdown processor notes
44+
45+
The site uses Sätteri through Astro's `markdown.processor` API:
46+
47+
```js
48+
import { satteri } from '@astrojs/markdown-satteri';
49+
50+
export default defineConfig({
51+
markdown: {
52+
processor: satteri(),
53+
},
54+
});
55+
```
56+
57+
Do not assume arbitrary Remark/Rehype plugin behavior unless it has been tested with Sätteri. If a future docs feature needs Markdown plugins, validate the build and the rendered Pages output before merging.
1558

1659
## Environment
1760

docs/src/pages/docs/[...slug].astro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const SLUG_LABEL: Record<string, string> = {
99
challenge: 'Challenge',
1010
'ci-cd-pipeline': 'CI/CD Pipeline',
1111
'getting-started': 'Getting Started',
12+
'api-reference': 'API Reference',
1213
performance: 'Performance',
1314
};
1415
@@ -17,6 +18,7 @@ const SECTION_SUMMARIES: Record<string, { eyebrow: string; description: string;
1718
challenge: { eyebrow: 'Spec', description: 'Rules, endpoints, validation behavior, and resource envelope.', signal: '1.5 CPU / 550MB' },
1819
architecture: { eyebrow: 'Runtime', description: 'NGINX, two Native AOT API containers, PostgreSQL stored procedures, and telemetry.', signal: 'API x2 + PostgreSQL' },
1920
'getting-started': { eyebrow: 'Runbook', description: 'Clone, boot the compose stack, smoke `/healthz`, and issue sample requests.', signal: 'copy-ready' },
21+
'api-reference': { eyebrow: 'Contract', description: 'Endpoint payloads, response shapes, validation rules, and current implementation notes.', signal: 'HTTP 200 / 404 / 422' },
2022
performance: { eyebrow: 'Evidence', description: 'Budget split, latency choices, k6 lane, and release validation links.', signal: 'AOT + k6' },
2123
'ci-cd-pipeline': { eyebrow: 'Release', description: 'PR gates, GHCR publishing, CodeQL, and Pages deploy workflow.', signal: 'linear main' },
2224
};

docs/src/pages/reports/index.astro

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ function parseReportMeta(filename: string) {
3737
<main class="reports-main">
3838
<header class="reports-header">
3939
<h1>Stress Test Reports</h1>
40-
<p>{reports.length} runs archived — k6 load tests executed on every push to <code>main</code></p>
40+
<p>{reports.length} runs archived — k6 load-test HTML reports preserved from release validation runs and benchmark experiments.</p>
41+
<p class="reports-note">Each file is an immutable report artifact. Use the docs performance page for methodology and only update homepage benchmark numbers when they can be traced to a specific archived report.</p>
4142
</header>
4243

4344
<div class="reports-list">
@@ -101,6 +102,12 @@ function parseReportMeta(filename: string) {
101102
font-size: 0.9rem;
102103
}
103104

105+
.reports-note {
106+
margin-top: 0.75rem;
107+
max-width: 720px;
108+
line-height: 1.6;
109+
}
110+
104111
.reports-header code {
105112
background: rgba(10, 22, 40, 0.8);
106113
border: 1px solid var(--rust-dark);

docs/src/sidebar.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export const SECTION_CATEGORIES = [
22
{ label: "", ids: ["home"] },
33
{ label: "Overview", ids: ["challenge", "architecture"] },
4-
{ label: "Develop", ids: ["getting-started", "performance", "ci-cd-pipeline"] },
4+
{ label: "Develop", ids: ["getting-started", "api-reference", "performance", "ci-cd-pipeline"] },
55
] as const;
66

77
export const SECTION_ORDER = SECTION_CATEGORIES.flatMap(({ ids }) => ids);

docs/wiki/api-reference.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# API Reference
2+
3+
## Base URL
4+
5+
Local and CI smoke tests reach the API through NGINX:
6+
7+
```text
8+
http://localhost:9999
9+
```
10+
11+
The production compose file exposes the same public entrypoint on port `9999`.
12+
13+
## Client IDs and limits
14+
15+
The implementation has five predefined clients. The API keeps this small map in memory for fast invalid-client rejection, and PostgreSQL seeds the same limits in `docker-entrypoint-initdb.d/rinha.dump.sql`.
16+
17+
| Client ID | Limit, in cents |
18+
|---:|---:|
19+
| `1` | `100000` |
20+
| `2` | `80000` |
21+
| `3` | `1000000` |
22+
| `4` | `10000000` |
23+
| `5` | `500000` |
24+
25+
## `POST /clientes/{id}/transacoes`
26+
27+
Submits a credit or debit transaction.
28+
29+
### Request body
30+
31+
```json
32+
{
33+
"valor": 1000,
34+
"tipo": "c",
35+
"descricao": "deposito"
36+
}
37+
```
38+
39+
| Field | Type | Rule |
40+
|---|---|---|
41+
| `valor` | integer | Required positive integer amount in cents (`> 0`) |
42+
| `tipo` | string | Required; `"c"` for credit or `"d"` for debit |
43+
| `descricao` | string | Required, non-empty, maximum 10 characters |
44+
45+
### Successful response
46+
47+
The response uses `snake_case` JSON output generated by `System.Text.Json` source generation.
48+
49+
```json
50+
{
51+
"id": 1,
52+
"limite": 100000,
53+
"saldo": 1000
54+
}
55+
```
56+
57+
### Status codes
58+
59+
| Status | When |
60+
|---:|---|
61+
| `200` | Transaction accepted and a balance is returned |
62+
| `404` | Client ID is not one of `1` through `5` |
63+
| `422` | Payload validation fails (`valor <= 0`, invalid `tipo`, missing/empty/long `descricao`) |
64+
65+
### Current implementation note
66+
67+
The intended Rinha contract treats a debit that would exceed the client's limit as an unprocessable transaction. The current PostgreSQL function keeps the balance unchanged and returns the current balance when the limit update fails, so the API can return `200` with an unchanged balance for this case. If strict challenge behavior is required, adjust `InsertTransacao`/the route handler before documenting over-limit debits as guaranteed `422` responses.
68+
69+
## `GET /clientes/{id}/extrato`
70+
71+
Returns the current account statement.
72+
73+
### Successful response
74+
75+
```json
76+
{
77+
"saldo": {
78+
"total": 1000,
79+
"limite": 100000,
80+
"data_extrato": "2026-04-01T19:20:20.000000"
81+
},
82+
"ultimas_transacoes": [
83+
{
84+
"valor": 1000,
85+
"tipo": "c",
86+
"descricao": "deposito"
87+
}
88+
]
89+
}
90+
```
91+
92+
The statement returns the latest 10 transactions ordered from newest to oldest.
93+
94+
### Status codes
95+
96+
| Status | When |
97+
|---:|---|
98+
| `200` | Client exists and statement data was returned |
99+
| `404` | Client ID is not one of `1` through `5` |
100+
101+
## `GET /healthz`
102+
103+
Health check used by local smoke tests and GitHub Actions.
104+
105+
| Status | When |
106+
|---:|---|
107+
| `200` | ASP.NET Core health checks pass |
108+
109+
## Responsibility split
110+
111+
| Layer | Responsibility |
112+
|---|---|
113+
| API (`Program.cs`) | Route mapping, JSON serialization, payload validation, fast invalid-client checks, database calls |
114+
| PostgreSQL (`rinha.dump.sql`) | Seed clients, keep balances, apply atomic balance updates, insert transaction rows, return recent statement data |
115+
| NGINX (`nginx.conf`) | Public port `9999` and load balancing across the two API instances |

docs/wiki/architecture.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ The API project targets `net9.0`; its Dockerfile currently builds and runs it wi
4242

4343
## Database boundary
4444

45-
PostgreSQL owns the race-sensitive work. The API calls stored procedures for balance reads and transaction inserts instead of doing multi-step application-side coordination.
45+
PostgreSQL owns the race-sensitive work. The API calls stored procedures for balance reads and transaction inserts instead of doing multi-step application-side coordination. Payload-shape validation remains API-side; the stored procedure should be read as the atomic consistency boundary, not as the only validation layer.
4646

4747
| Procedure | Responsibility |
4848
|-----------|----------------|
49-
| `InsertTransacao` | Validate client, type, value, description, and credit limit, then insert atomically |
49+
| `InsertTransacao` | Check client existence, apply the atomic balance update, enforce the database-side credit-limit guard, and insert the transaction row when the update succeeds |
5050
| `GetSaldoClienteById` | Return balance metadata and recent transactions as JSONB |
5151

5252
The compose command also tunes write durability for the contest setting:
@@ -77,3 +77,17 @@ That shape keeps database concurrency explicit, avoids unbounded connection grow
7777
| `TRIM` | `false` | Leaves trimming separate from AOT path |
7878
| `EXTRA_OPTIMIZE` | `true` | Removes observability/runtime support guarded by `EXTRAOPTIMIZE` |
7979
| `BUILD_CONFIGURATION` | `Release` | Uses optimized .NET build configuration |
80+
81+
82+
## API and database contract
83+
84+
| Rule | Enforced by | Source |
85+
|------|-------------|--------|
86+
| Client ID is one of `1..5` | API fast path and database existence check | `Program.cs`, `InsertTransacao` |
87+
| `valor` is positive | API | `IsTransacaoValid` |
88+
| `tipo` is exactly `c` or `d` | API | `IsTransacaoValid` |
89+
| `descricao` is present and <= 10 chars | API, with SQL parameter width as a backstop | `IsTransacaoValid`, `InsertTransacao(... descricao VARCHAR(10))` |
90+
| Balance update and transaction insert stay atomic | PostgreSQL | `InsertTransacao` |
91+
| Statement returns newest 10 transactions | PostgreSQL | `GetSaldoClienteById` |
92+
93+
The NGINX config currently uses `least_conn`. If this repository is used as a strict official challenge submission, keep the config comments and docs aligned with whatever balancing policy is allowed by the target ruleset.

docs/wiki/challenge.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,18 @@ Rinha de Backend is a Brazilian backend challenge focused on constrained, concur
88

99
| Endpoint | Method | Purpose | Expected statuses |
1010
|----------|--------|---------|-------------------|
11-
| `/clientes/{id}/transacoes` | POST | Submit a debit (`d`) or credit (`c`) transaction for client IDs 1 through 5 | `200`, `404`, `422` |
11+
| `/clientes/{id}/transacoes` | POST | Submit a debit (`d`) or credit (`c`) transaction for client IDs 1 through 5 | `200`, `404`, `422` for invalid payloads |
1212
| `/clientes/{id}/extrato` | GET | Return balance, credit limit, statement time, and recent transactions | `200`, `404` |
1313
| `/healthz` | GET | Local and CI health check for this implementation | `200` |
1414

1515
## Validation rules
1616

1717
- Only client IDs `1` through `5` exist.
1818
- Transaction type must be debit (`d`) or credit (`c`).
19-
- Transaction value must be an integer amount.
20-
- Description must be present and at most 10 characters, matching the current API validation and database function signature.
21-
- Debits cannot push the account beyond the configured credit limit.
19+
- Transaction value must be a positive integer amount in cents (`valor > 0`).
20+
- Description must be present, non-empty, and at most 10 characters.
21+
- Transaction type must be checked by the API before the request reaches PostgreSQL.
22+
- Debits are intended not to push the account beyond the configured credit limit.
2223
- Statement responses return the current balance plus the latest 10 transactions.
2324

2425
## Resource envelope
@@ -42,3 +43,13 @@ The k6 runner and observability containers are test infrastructure. They are use
4243
## Source
4344

4445
Full specification: [github.com/zanfranceschi/rinha-de-backend-2024-q1](https://github.com/zanfranceschi/rinha-de-backend-2024-q1)
46+
47+
48+
## Current implementation compatibility note
49+
50+
Payload validation is split across the API and PostgreSQL:
51+
52+
- `Program.cs` rejects invalid client IDs, non-positive values, invalid transaction types, and missing/empty/long descriptions before calling the database.
53+
- `InsertTransacao` in `docker-entrypoint-initdb.d/rinha.dump.sql` performs the atomic balance update and transaction insert.
54+
55+
One important edge case is worth calling out: when a debit would exceed the account limit, the current SQL function returns the existing balance without inserting a transaction. Because the route handler treats the returned balance as a successful result, this case can surface as `200 OK` with an unchanged balance rather than the strict Rinha `422` behavior. Treat this as a known implementation note until the SQL/API contract is changed.

docs/wiki/ci-cd-pipeline.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,23 @@ After a PR is rebased into `main`, the release workflow publishes the runtime im
4545
| Docs site | `https://jonathanperis.github.io/rinha2-back-end-dotnet/` |
4646
| Docs section | `https://jonathanperis.github.io/rinha2-back-end-dotnet/docs/` |
4747

48+
## Build/version matrix
49+
50+
| Path | SDK/runtime | Flags | Purpose |
51+
|------|-------------|-------|---------|
52+
| Local Docker/dev compose | .NET `10.0` SDK/runtime images from `src/WebApi/Dockerfile` | `AOT=true`, `TRIM=false`, `EXTRA_OPTIMIZE=false` | Local stack with OpenTelemetry support |
53+
| PR Build Check | `actions/setup-dotnet` `9.0.x` | `AOT=true`, `TRIM=false`, `EXTRA_OPTIMIZE=true` | Fast compile gate plus compose health check |
54+
| Main Release image | .NET `10.0` Docker build/runtime images | `AOT=true`, `TRIM=false`, `EXTRA_OPTIMIZE=true` | GHCR release image and multi-arch manifest |
55+
| CodeQL | `actions/setup-dotnet` `9.0.x` | Plain Release build | Security analysis |
56+
| Docs deploy | Bun + Astro `^6.4.2` | `NODE_ENV=production` base path | GitHub Pages static site |
57+
58+
The project targets `net9.0` even when Docker build images are `10.0`.
59+
4860
## Documentation deploy
4961

50-
The Pages workflow delegates to Jonathan's shared GitHub Pages workflow and uses Bun as the package manager. The docs package is an Astro static site under `docs/`; Markdown content lives in `docs/wiki/`, and production routes are served under the `/rinha2-back-end-dotnet` base path.
62+
The Pages workflow delegates to Jonathan's shared GitHub Pages workflow and uses Bun as the package manager. The docs package is an Astro 6.4 static site under `docs/`; Markdown content lives in `docs/wiki/`, and production routes are served under the `/rinha2-back-end-dotnet` base path.
63+
64+
The docs site uses Astro's `markdown.processor` API with `@astrojs/markdown-satteri`. That keeps Markdown rendering explicit, but it also means future Remark/Rehype-style extensions should be tested against Sätteri before they are assumed to work.
5165

5266
## Operational notes
5367

0 commit comments

Comments
 (0)