docs: update documentation to OKF v0.1 format

This commit is contained in:
OpenVelo Agent
2026-07-23 16:14:03 +00:00
parent e81c568fca
commit e9020dfa23
5 changed files with 155 additions and 6 deletions
+7 -1
View File
@@ -3,7 +3,7 @@ type: architecture
title: Backend Module Map title: Backend Module Map
description: NestJS modules, controllers, services, and how they are wired together. description: NestJS modules, controllers, services, and how they are wired together.
tags: [architecture, backend, nestjs, modules] tags: [architecture, backend, nestjs, modules]
timestamp: 2026-07-23T15:46:55Z timestamp: 2026-07-23T16:10:00Z
--- ---
# Module Map # Module Map
@@ -86,6 +86,11 @@ also registers two global providers:
`DatabaseInitService.init()``FilesystemTransactionService.recoverTransactions()` `DatabaseInitService.init()``FilesystemTransactionService.recoverTransactions()`
(resolves interrupted restore manifests before TypeORM opens the live SQLite file; (resolves interrupted restore manifests before TypeORM opens the live SQLite file;
ambiguous generations abort startup) → stale unowned swap-artifact sweep → migrations + WAL ambiguous generations abort startup) → stale unowned swap-artifact sweep → migrations + WAL
`verifySeed()` (best-effort)
`ensureSystemCategoryIcons()` (best-effort — writes
`CRY/HW/MSC/PWN/REV/WEB.png` into `<UPLOAD_DIR>/icons/` if missing so
freshly bootstrapped instances never serve 404 icons; see
[System Category Icon Seed](/architecture/system-category-icons.md))
`app.use(...)` middlewares (helmet, parsers, CSRF, static) `app.use(...)` middlewares (helmet, parsers, CSRF, static)
`SwaggerModule.setup(...)` (OpenAPI 3.1) `SwaggerModule.setup(...)` (OpenAPI 3.1)
`SpaFallbackMiddleware``app.listen()`. `SpaFallbackMiddleware``app.listen()`.
@@ -94,6 +99,7 @@ ambiguous generations abort startup) → stale unowned swap-artifact sweep → m
- [System Overview](/architecture/overview.md) - [System Overview](/architecture/overview.md)
- [Key Files Index](/architecture/key-files.md) - [Key Files Index](/architecture/key-files.md)
- [System Category Icon Seed](/architecture/system-category-icons.md)
- [REST API Overview](/api/rest-overview.md) - [REST API Overview](/api/rest-overview.md)
- [Blog API](/api/blog.md) - [Blog API](/api/blog.md)
- [Admin System API](/api/admin-system.md) - [Admin System API](/api/admin-system.md)
+5 -2
View File
@@ -3,7 +3,7 @@ type: architecture
title: Key Files Index title: Key Files Index
description: One-line responsibility for important source and contract-test files, including event-window, challenge, scoreboard, session, notification, and blog flows. description: One-line responsibility for important source and contract-test files, including event-window, challenge, scoreboard, session, notification, and blog flows.
tags: [architecture, key-files, event-window, validation, default-challenge-ip, sse, bootstrap, migrations, challenges, import, board, notifications, per-user, session, blog] tags: [architecture, key-files, event-window, validation, default-challenge-ip, sse, bootstrap, migrations, challenges, import, board, notifications, per-user, session, blog]
timestamp: 2026-07-23T15:46:55Z timestamp: 2026-07-23T16:10:00Z
--- ---
# Backend # Backend
@@ -14,7 +14,8 @@ timestamp: 2026-07-23T15:46:55Z
| `backend/src/app.module.ts` | Wires feature modules and global providers. | | `backend/src/app.module.ts` | Wires feature modules and global providers. |
| `backend/src/config/env.schema.ts` | Zod-validated runtime config; canonical defaults for `DATABASE_PATH` (`./data/db.sqlite`) and `UPLOAD_DIR` (`./data/uploads`). | | `backend/src/config/env.schema.ts` | Zod-validated runtime config; canonical defaults for `DATABASE_PATH` (`./data/db.sqlite`) and `UPLOAD_DIR` (`./data/uploads`). |
| `backend/src/database/database.module.ts` | Configures TypeORM with SQLite and imports the shared `FilesystemTransactionModule` used by pre-TypeORM startup recovery. | | `backend/src/database/database.module.ts` | Configures TypeORM with SQLite and imports the shared `FilesystemTransactionModule` used by pre-TypeORM startup recovery. |
| `backend/src/database/database-init.service.ts` | Before opening SQLite, resolves durable swap manifests to a coherent old or verified-new database/uploads generation, fails startup closed on ambiguity, best-effort sweeps stale unowned swap artifacts, then runs migrations, enforces WAL, and verifies seeds. | | `backend/src/database/database-init.service.ts` | Before opening SQLite, resolves durable swap manifests to a coherent old or verified-new database/uploads generation, fails startup closed on ambiguity, best-effort sweeps stale unowned swap artifacts, then runs migrations, enforces WAL, verifies seeds, and best-effort seeds the canonical system-category icon PNGs into `UPLOAD_DIR/icons` so a fresh clone never serves 404 icons. |
| `backend/src/database/system-category-icons.ts` | Exports `CANONICAL_SYSTEM_ICON_KEYS` (`['CRY','HW','MSC','PWN','REV','WEB']`), `SYSTEM_ICON_SIZE` (`128`), and `seedSystemCategoryIcons(uploadDir)`: creates `<uploadDir>/icons/` recursively and writes a deterministic sharp 128×128 PNG for every missing canonical key (palette-coloured background + white abbreviation overlay). Idempotent — existing non-empty files are reported as `skipped` so admin uploads are preserved across restarts. |
| `backend/src/modules/admin/system/filesystem-transaction.module.ts` | Exports one shared `FilesystemTransactionService` provider to `DatabaseModule` and `AdminSystemModule`, avoiding a circular dependency. | | `backend/src/modules/admin/system/filesystem-transaction.module.ts` | Exports one shared `FilesystemTransactionService` provider to `DatabaseModule` and `AdminSystemModule`, avoiding a circular dependency. |
| `backend/src/database/migrations/1700000000400-RepairCategorySchemaAndSystemCategories.ts` | Forward-only repair migration: adds `created_at`/`updated_at` to legacy six-column `category` tables, deletes obsolete `FOR`/`OSI` system rows, rewrites canonical row metadata, inserts any missing canonical key, and re-asserts unique indexes. Exports `CANONICAL_SYSTEM_CATEGORIES`. | | `backend/src/database/migrations/1700000000400-RepairCategorySchemaAndSystemCategories.ts` | Forward-only repair migration: adds `created_at`/`updated_at` to legacy six-column `category` tables, deletes obsolete `FOR`/`OSI` system rows, rewrites canonical row metadata, inserts any missing canonical key, and re-asserts unique indexes. Exports `CANONICAL_SYSTEM_CATEGORIES`. |
| `backend/src/common/guards/jwt-auth.guard.ts` | Global JWT authorization guard. | | `backend/src/common/guards/jwt-auth.guard.ts` | Global JWT authorization guard. |
@@ -145,6 +146,7 @@ timestamp: 2026-07-23T15:46:55Z
| `tests/backend/admin-system-restore-validation.spec.ts` | Archive schema validation, base64 size mismatch, sha256 mismatch, safe-path rejection, and duplicate-path rejection. | | `tests/backend/admin-system-restore-validation.spec.ts` | Archive schema validation, base64 size mismatch, sha256 mismatch, safe-path rejection, and duplicate-path rejection. |
| `tests/backend/admin-system-restore-commit.spec.ts` | Controller forwarding, real-file restore, trigger preservation, admin-less archive rejection, and kill-during-swap regression proving restart recovers one coherent database/uploads generation. | | `tests/backend/admin-system-restore-commit.spec.ts` | Controller forwarding, real-file restore, trigger preservation, admin-less archive rejection, and kill-during-swap regression proving restart recovers one coherent database/uploads generation. |
| `tests/backend/database-init-recovery.spec.ts` | Startup recovery contracts: restores old generations from interrupted manifests, fails closed without mutation on hybrid state, and removes stale unowned artifacts. | | `tests/backend/database-init-recovery.spec.ts` | Startup recovery contracts: restores old generations from interrupted manifests, fails closed without mutation on hybrid state, and removes stale unowned artifacts. |
| `tests/backend/system-category-icons.spec.ts` | Pure helper contract for `seedSystemCategoryIcons`: writes six valid 128×128 PNGs in a fresh temp dir; second invocation reports all six as `skipped` and leaves an admin-uploaded `CRY.png` `mtimeMs` unchanged; creates the `icons/` subdirectory when the parent is empty. Real `sharp` + `os.tmpdir()`, no mocks. |
| `tests/backend/filesystem-transaction.spec.ts` | Durable manifest phase, commit/rollback, old/new generation recovery, ambiguous fail-closed, and stale artifact sweep contracts. | | `tests/backend/filesystem-transaction.spec.ts` | Durable manifest phase, commit/rollback, old/new generation recovery, ambiguous fail-closed, and stale artifact sweep contracts. |
| `frontend/src/app/features/admin/system/system.component.ts` | `/admin/system` smart page: two panels (Database + Danger Zone), backup download, restore pick + validate, confirm modal trigger, and per-operation success/error surfacing + cross-store invalidation. | | `frontend/src/app/features/admin/system/system.component.ts` | `/admin/system` smart page: two panels (Database + Danger Zone), backup download, restore pick + validate, confirm modal trigger, and per-operation success/error surfacing + cross-store invalidation. |
| `frontend/src/app/features/admin/system/system-confirm-modal.component.ts` | Re-authentication + confirmation-phrase modal with operation-specific copy from `system.pure.ts`. | | `frontend/src/app/features/admin/system/system-confirm-modal.component.ts` | Re-authentication + confirmation-phrase modal with operation-specific copy from `system.pure.ts`. |
@@ -162,6 +164,7 @@ timestamp: 2026-07-23T15:46:55Z
- [Frontend Structure](/architecture/frontend-structure.md) - [Frontend Structure](/architecture/frontend-structure.md)
- [Authenticated Shell](/guides/authenticated-shell.md) - [Authenticated Shell](/guides/authenticated-shell.md)
- [System Category Icon Seed](/architecture/system-category-icons.md)
- [System Endpoints](/api/system.md) - [System Endpoints](/api/system.md)
- [Challenges Endpoints](/api/challenges.md) - [Challenges Endpoints](/api/challenges.md)
- [Scoreboard Page Guide](/guides/scoreboard-page.md) - [Scoreboard Page Guide](/guides/scoreboard-page.md)
+135
View File
@@ -0,0 +1,135 @@
---
type: architecture
title: System Category Icon Seed
description: Idempotent startup hook that writes deterministic 128×128 PNG icons for the canonical six system categories (CRY/HW/MSC/PWN/REV/WEB) into UPLOAD_DIR/icons, so freshly bootstrapped instances never serve 404 icons.
tags: [architecture, startup, categories, icons, seed, sharp, migrations, tester]
timestamp: 2026-07-23T16:10:00Z
---
# Purpose
The canonical six system categories are seeded by the
`UpdateSystemCategoryKeys1700000000300` and
`RepairCategorySchemaAndSystemCategories1700000000400` migrations, which
insert database rows whose `icon_path` column references
`/uploads/icons/{CRY,HW,MSC,PWN,REV,WEB}.png`. The migrations create the
DB rows but never write the PNG assets to disk, and `setup.sh` only
`mkdir -p`s the upload directory — it does not populate it.
As a result, `GET /uploads/icons/CRY.png` returns `404` on a freshly
cloned instance, and the challenges board and admin categories page
show broken images for every system row until an admin re-uploads each
icon.
This module closes that gap by generating a deterministic 128×128 PNG
for each canonical key on every backend startup, idempotently. Admin
uploads written through `UploadsController` are never overwritten.
# Files
| File | Responsibility |
|---|---|
| `backend/src/database/system-category-icons.ts` | Exports `CANONICAL_SYSTEM_ICON_KEYS` (`['CRY','HW','MSC','PWN','REV','WEB']`), `SYSTEM_ICON_SIZE` (`128`), and the async `seedSystemCategoryIcons(uploadDir)` helper. Creates `<uploadDir>/icons/` recursively, generates a sharp 128×128 PNG (palette-coloured background + white 26-letter abbreviation overlay) for every key whose `<KEY>.png` does not yet exist or is zero-bytes, and returns `{ written, skipped }`. |
| `backend/src/database/database-init.service.ts` | Calls `seedSystemCategoryIcons(path.resolve(config.get('UPLOAD_DIR', './data/uploads')))` from `init()` after migrations and `verifySeed()`. Skips when `UPLOAD_DIR` is unset or contains `:memory:`. Failures log a `WARN` and never abort startup. |
| `tests/backend/system-category-icons.spec.ts` | Pure helper contract: writes six 128×128 PNGs in a fresh temp dir; second run reports all six as `skipped` and leaves `mtimeMs` of an existing admin-uploaded icon unchanged; creates the `icons/` subdirectory when the parent is empty. |
# How the helper works
`seedSystemCategoryIcons(uploadDir)`:
1. Resolves `iconsDir = path.join(uploadDir, 'icons')` and creates it
recursively when missing — covers the fresh-clone case where the
directory does not yet exist.
2. For each `key` in `CANONICAL_SYSTEM_ICON_KEYS`:
* `target = path.join(iconsDir, `${key}.png`)`.
* If the file exists **and** its size is `> 0`, push `target` onto
`skipped` and continue. This is the contract that protects
admin-uploaded icons: a later restart must never overwrite them.
* Otherwise build a sharp PNG with:
* 128×128 RGBA buffer,
* background colour from `PALETTE[hash(key) % PALETTE.length]`
(8-entry palette, deterministic hash so every icon is visually
distinct but reproducible across machines/containers),
* white abbreviation overlay drawn from
`renderLabel(key)` (one white pixel per letter, centred).
* Write the buffer with `fs.writeFileSync(target, buf)` and push
`target` onto `written`.
3. Return `{ written, skipped }`.
The palette and overlay are intentionally simple — these are
fallback/placeholder icons. An admin who wants branded artwork uploads
a real image through `POST /api/v1/uploads/category-icon`, which writes
to the same `<uploadDir>/icons/<KEY>.png` path and is then preserved on
subsequent restarts.
# Startup wiring
Inside `DatabaseInitService.init()` the new step runs after the
existing `runMigrations` block and `verifySeed()`:
```
verifySeed() // best-effort WARN on failure
ensureSystemCategoryIcons() // best-effort WARN on failure
```
`ensureSystemCategoryIcons()` short-circuits with a `debug` log when
`UPLOAD_DIR` is unset or contains `:memory:` (matches how the module
already guards in-memory test databases). Otherwise it logs:
```
System category icons: written=<n> skipped=<n>
```
A failure logs `ensureSystemCategoryIcons skipped: <message>` as a
`WARN` and **does not abort startup** — mirroring the
`verifySeed` "best-effort" pattern. Missing icons are recoverable (they
only affect image rendering on a few pages) and never block HTTP
traffic.
# Source-of-truth coupling
`CANONICAL_SYSTEM_ICON_KEYS` is duplicated from
`CANONICAL_SYSTEM_CATEGORIES` in
`backend/src/database/migrations/1700000000400-RepairCategorySchemaAndSystemCategories.ts:13`
to avoid a migration → runtime import cycle (the migration is
registered after `database.module.ts` is built, and re-importing from
the migration would be circular). The duplication is **intentional**;
the helper file carries a top-of-file comment pointing at the
canonical migration as the source of truth, and the helper, the
migration, and the hard-coded `iconPath` URLs in
`1700000000300-UpdateSystemCategoryKeys.ts` and
`1700000000400-RepairCategorySchemaAndSystemCategories.ts` must be kept
in sync whenever a system category is added or renamed.
# What to look at next
- The `UPLOAD_DIR` value is read from `ConfigService` and defaults to
`./data/uploads` (see
[`env.schema.ts`](/architecture/key-files.md) for the canonical
defaults).
- The icons directory is served by `app.use('/uploads', express.static(uploadDir))`
in `backend/src/main.ts:79`, so the on-disk files are the same
bytes returned by `GET /uploads/icons/<KEY>.png` to the SPA.
- Admin overrides go through
[`POST /api/v1/uploads/category-icon`](/api/uploads.md), which
normalizes uploaded images to the same 128×128 PNG size and writes
to the same `UPLOAD_DIR/icons/<KEY>.png` path.
- The challenges board and admin categories page consume
`<img src="/uploads/icons/<KEY>.png">` from each `category.icon_path`
column — see [Challenges Board](/guides/challenges-board.md) and
[Admin — Categories](/guides/admin-categories.md).
# Tests
| File | What it asserts |
|---|---|
| `tests/backend/system-category-icons.spec.ts` | Real `sharp` + `os.tmpdir()`: writes six PNGs of `format === 'png'` and `width === height === 128` on a fresh dir; second invocation reports all six as `skipped` and does not change `mtimeMs` of an existing `CRY.png` (admin-upload contract); creates the `icons/` subdirectory when the parent is empty. No mocks. |
# See also
- [Category Repair Migration](/database/category-repair-migration.md) — canonical six system categories, DB-side.
- [Uploads Endpoints](/api/uploads.md) — admin override path for the same on-disk files.
- [Challenges Board](/guides/challenges-board.md) — tester-visible consumption of the icons.
- [Admin — Categories](/guides/admin-categories.md) — admin override UI.
- [Backend Module Map](/architecture/backend-modules.md) — startup chain.
- [Key Files Index](/architecture/key-files.md) — file-by-file responsibilities.
+3 -2
View File
@@ -3,7 +3,7 @@ type: guide
title: Challenges Board title: Challenges Board
description: How a signed-in player navigates the `/challenges` page, opens a challenge modal, submits a flag, and watches live solve updates. description: How a signed-in player navigates the `/challenges` page, opens a challenge modal, submits a flag, and watches live solve updates.
tags: [guide, challenges, board, flag, score, sse, tester] tags: [guide, challenges, board, flag, score, sse, tester]
timestamp: 2026-07-23T04:05:00Z timestamp: 2026-07-23T16:10:00Z
--- ---
# When this view is available # When this view is available
@@ -55,7 +55,7 @@ category.
| Element | Selector | Expected | | Element | Selector | Expected |
|----------------------------------------|---------------------------------------------|-----------------------------------------------------------------------------------------------------------------| |----------------------------------------|---------------------------------------------|-----------------------------------------------------------------------------------------------------------------|
| Category column | `[data-testid="category-column-CR"]` (or any abbreviation) | Header shows the category `icon`, uppercase `abbreviation`, and full `name`; below it is a vertical list of cards. | | Category column | `[data-testid="category-column-CR"]` (or any abbreviation) | Header shows the category `icon`, uppercase `abbreviation`, and full `name`; below it is a vertical list of cards. The icon's `src` is `/uploads/icons/<KEY>.png` and is served by the backend's static middleware; on a freshly bootstrapped instance these PNGs are generated at startup by [`seedSystemCategoryIcons`](/architecture/system-category-icons.md) so the column headers never show broken images. |
| Category column with no challenges | Same | The header renders but the body shows `No challenges yet.` | | Category column with no challenges | Same | The header renders but the body shows `No challenges yet.` |
| Challenge card | `[data-testid="challenge-card-<uuid>"]` | Rendered as a real `<button type="button">`. Keyboard activatable (Tab + Enter/Space), exposes `aria-pressed` and an accessible name `Challenge <name>, solved` / `Challenge <name>, not solved`, and `data-solved="true"\|"false"`. Click anywhere (or press Enter/Space) to open the modal. | | Challenge card | `[data-testid="challenge-card-<uuid>"]` | Rendered as a real `<button type="button">`. Keyboard activatable (Tab + Enter/Space), exposes `aria-pressed` and an accessible name `Challenge <name>, solved` / `Challenge <name>, not solved`, and `data-solved="true"\|"false"`. Click anywhere (or press Enter/Space) to open the modal. |
| Difficulty pill | `.diff.diff-LOW` / `.diff-MEDIUM` / `.diff-HIGH` | Green / yellow / red pill matching the challenge difficulty. | | Difficulty pill | `.diff.diff-LOW` / `.diff-MEDIUM` / `.diff-HIGH` | Green / yellow / red pill matching the challenge difficulty. |
@@ -215,6 +215,7 @@ destruction of the page the SSE source is closed via
- [Challenges Endpoints](/api/challenges.md) - [Challenges Endpoints](/api/challenges.md)
- [Challenge Tables](/database/challenges.md) - [Challenge Tables](/database/challenges.md)
- [System Endpoints](/api/system.md) (legacy `/events/status`) - [System Endpoints](/api/system.md) (legacy `/events/status`)
- [System Category Icon Seed](/architecture/system-category-icons.md) (how the column-header icon PNGs are guaranteed to exist on a fresh clone)
- [Scoreboard Stream](/guides/scoreboard-stream.md) - [Scoreboard Stream](/guides/scoreboard-stream.md)
- [Authenticated Shell](/guides/authenticated-shell.md) (SSE transport) - [Authenticated Shell](/guides/authenticated-shell.md) (SSE transport)
- [Per-User `solvedByMe` Across the Session Boundary](/guides/challenges-per-user-state.md) (logout/login, peer-tab, SSE-unauthorized reset) - [Per-User `solvedByMe` Across the Session Boundary](/guides/challenges-per-user-state.md) (logout/login, peer-tab, SSE-unauthorized reset)
+5 -1
View File
@@ -12,7 +12,7 @@ controls, administrator-managed Markdown blog publishing, and public and
signed-in blog views. signed-in blog views.
The docs below are organized by purpose so agents can pull just the slice The docs below are organized by purpose so agents can pull just the slice
they need. Last regenerated 2026-07-23T15:46:55Z. they need. Last regenerated 2026-07-23T16:10:00Z.
# Architecture # Architecture
@@ -25,6 +25,10 @@ they need. Last regenerated 2026-07-23T15:46:55Z.
+ signal stores). + signal stores).
* [Key Files Index](/architecture/key-files.md) - One-line summary of every * [Key Files Index](/architecture/key-files.md) - One-line summary of every
important source file in the repository. important source file in the repository.
* [System Category Icon Seed](/architecture/system-category-icons.md) -
Idempotent startup hook that writes deterministic 128×128 PNGs for
CRY/HW/MSC/PWN/REV/WEB into `UPLOAD_DIR/icons` so freshly cloned
instances never serve 404 icons.
# Database # Database