Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ba91b08
docs: inventory of all 533 HTTP endpoints
TaprootFreak Jul 31, 2026
b5cf50c
docs: require the endpoint inventory to be kept in sync
TaprootFreak Jul 31, 2026
6d1ce80
docs: one table for all endpoints, with eager and load-volume columns
TaprootFreak Jul 31, 2026
4614d2b
docs: drop the table layout description from the contribution rule
TaprootFreak Jul 31, 2026
41159d7
docs: fix two parser defects and separate n/a from unknown
TaprootFreak Aug 1, 2026
41c5539
docs: explain the read-path work and define how it is tested
TaprootFreak Aug 1, 2026
4973b20
docs: fix body extraction for destructured parameters
TaprootFreak Aug 1, 2026
ff19685
docs: resolve inherited write operations instead of marking them unknown
TaprootFreak Aug 1, 2026
7616dc9
docs: resolve all but two endpoints
TaprootFreak Aug 1, 2026
55b70f0
docs: classify every endpoint, no unknowns left
TaprootFreak Aug 1, 2026
8ce3b7b
docs: separate projected from no-database access
TaprootFreak Aug 1, 2026
5a6cd9d
docs: record that the database test mechanism already exists
TaprootFreak Aug 1, 2026
7762675
docs: replace the per-endpoint load column with a load-site inventory
TaprootFreak Aug 1, 2026
a693c0b
Record API version per endpoint and correct the inventory
TaprootFreak Aug 1, 2026
1f921f3
Record deprecation, name the judgement calls, exclude non-reading sta…
TaprootFreak Aug 1, 2026
58141ea
Record what the suite covers of the six reads that name their columns
TaprootFreak Aug 1, 2026
2d104e1
State the target and require coverage to be recorded per endpoint
TaprootFreak Aug 1, 2026
5bd728d
Bring the inventory up to the current base and resolve the CONTRIBUTI…
TaprootFreak Aug 1, 2026
f5b26ef
Move the endpoint obligation out of the numbered checklist
TaprootFreak Aug 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,14 @@ Every PR must include:
2. **Environment/Infrastructure updates** (config, environment variables)
3. **Service updates** (if DTOs/interfaces changed)
4. **Frontend synchronization** (if API contracts changed)
5. **Cron job inventory** (if a `@DfxCron` job was added, removed or re-scheduled) — [docs/cron-jobs.md](docs/cron-jobs.md)

Missing any of these = changes requested.

Routes carry a sixth obligation: any change to the set of endpoints — added, removed, renamed or
re-scoped — must be reflected in [docs/endpoints.md](docs/endpoints.md) in the same PR, together
with the `Tests` state of anything converted. See *Endpoint Inventory* below.

### Before Merge

- Fix all linter errors and warnings (never disable lint rules without justification)
Expand Down Expand Up @@ -510,6 +515,32 @@ export class SupportIssueController {
- Status 200 for GET (not 201)
- Plain string responses are annoying — return JSON objects

### Endpoint Inventory

[docs/endpoints.md](docs/endpoints.md) lists every route this service exposes. **Any change to the set of routes must be reflected there in the same PR** — adding, removing, renaming or re-scoping an endpoint, and equally a change to a `@Controller` base path, which moves every route beneath it.

Two details are easy to get wrong when editing the list by hand:

- a file may declare more than one `@Controller` class, and a route belongs to the scope that **precedes** it, not to the first one in the file — `custody.controller.ts` declares both `custody` and `custody/admin`
- `@Controller()` without an argument puts its routes at the root, not under a prefix
- a route's version comes from `@Version` on the handler, otherwise from the `@Controller` scope, otherwise the configured default — six paths exist twice under different versions, so method and path alone do not identify a row

To verify a change, compare against the routes the framework logs at startup: every `Mapped {<path>, <METHOD>}` line is one registered route. If a route you added does not appear there, it is not reachable — two routing decorators on the same handler, for instance, keep only one path.

[docs/load-sites.md](docs/load-sites.md) is the companion inventory: every place in the code that reads from the database, with the mechanism and the measured column count. It is generated, not hand-maintained, but the rule it documents is worth knowing before writing a query:

- the `find` family applies eager relations and expands them recursively — a plain `findOne()` on `UserData` already selects 253 columns across 8 joins
- `createQueryBuilder` does not, but still loads every column of the root entity unless `.select([...])` narrows it
- `.select('alias')` is **not** a projection — the argument is the entity alias, not a field list
- a query builder carrying `.update()`, `.delete()` or `.insert()` is a write statement and loads nothing — the same goes for a raw `SELECT pg_advisory_xact_lock(...)`, which returns no rows; neither is part of that inventory

The target state is that every read path selects the fields it returns and nothing more. Two rules apply while we get there, and both are checked in review:

- **A read path is converted only when its tests reach `4/4`** against the four levels in [docs/read-path-projections.md](docs/read-path-projections.md). A projection that drops a field does not crash — it answers 200 with a wrong value. Converting without the tests trades a slow query for a silent defect.
- **Record the state in the `Tests` column of [docs/endpoints.md](docs/endpoints.md) in the same PR that changes the code.** An unrecorded conversion is treated as untested.

See [docs/read-path-projections.md](docs/read-path-projections.md) for the reasoning, the criteria for converting an endpoint, and what each of the four levels asserts.

### Cron Jobs

Use `@DfxCron` (custom wrapper with built-in locking, process control, and error handling). It replaces `@Cron` + `@Lock` + `DisabledProcess` — never combine these manually with `@DfxCron`.
Expand All @@ -530,6 +561,12 @@ async processPayments(): Promise<void> {
}
```

Declare a `process` flag unless the job maintains the disabled set itself. Without one the job
runs unconditionally and cannot be switched off without a deploy.

[docs/cron-jobs.md](docs/cron-jobs.md) lists every scheduled job with its interval and flag.
**Adding, removing or re-scheduling a job must be reflected there in the same PR.**

Prefer longer intervals (15min) over aggressive polling (1min). Only use short intervals when truly needed.

### Await Discipline
Expand Down
Loading