docs: update documentation to OKF v0.1 format

This commit is contained in:
OpenVelo Agent
2026-07-23 02:24:40 +00:00
parent cbfa9c35ca
commit ceebaa06aa
5 changed files with 159 additions and 2 deletions
@@ -0,0 +1,133 @@
---
type: database
title: Seed Sample Challenges Migration (1700000000600)
description: Forward-only migration that inserts a representative sample set of challenges on a fresh database so the /challenges board renders cards without manual admin action.
tags: [database, migration, challenge, seed, sample]
timestamp: 2026-07-23T02:23:34Z
---
# Purpose
A brand-new HIPCTF database (default path
[`./data/db.sqlite`](/database/schema.md)) has no `challenge` rows until
an admin imports or creates them. The
`SeedSampleChallenges1700000000600` migration bridges that gap by
inserting a small, representative sample set so the authenticated
`/challenges` board (see [Challenges Board](/guides/challenges-board.md))
already has cards on the very first visit and the public landing can
demonstrate the platform with real content.
The migration runs at the end of the `MIGRATIONS` array in
[`DatabaseModule`](/architecture/backend-modules.md) so every other
category / challenge schema migration has already landed and the
`category` rows it joins against are present.
# File
`backend/src/database/migrations/1700000000600-SeedSampleChallenges.ts`
Registered last in `backend/src/database/database.module.ts` so it
runs after `UpgradeChallengeAdminSchema1700000000500`.
# Sample set
Eight challenges, one or more per canonical system category. Source
of truth is the typed `SAMPLE_CHALLENGES` array exported by the
migration:
| Name | `system_key` | Difficulty | `initial_points` | `minimum_points` | `decay_solves` | Flag |
|-------------------|--------------|------------|------------------|------------------|----------------|--------------|
| Alpha Cipher | `CRY` | `LOW` | 200 | 100 | 5 | `flag{alpha}` |
| Beta Cipher | `CRY` | `MEDIUM` | 300 | 100 | 10 | `flag{beta}` |
| Zeta Key | `CRY` | `HIGH` | 500 | 100 | 10 | `flag{zeta}` |
| Web Welcome | `WEB` | `LOW` | 100 | 50 | 10 | `flag{web1}` |
| Mobile Mayhem | `PWN` | `MEDIUM` | 300 | 100 | 10 | `flag{mob1}` |
| Hardware Hello | `HW` | `LOW` | 100 | 50 | 10 | `flag{hw1}` |
| Markdown Mystery | `MSC` | `LOW` | 100 | 50 | 10 | `flag{md}` |
| Reverse Ranger | `REV` | `MEDIUM` | 300 | 100 | 10 | `flag{rev1}` |
All rows are inserted with `protocol='WEB'`, `port=NULL`,
`ip_address=''`, and `enabled=1` so they appear on the board the
moment the event window opens (see [Event Window](/guides/event-window.md)).
Categories are resolved by `system_key` rather than UUID so the seed
works regardless of the UUIDs the category migration assigned at
install time. Sample flags follow the `flag{<token>}` convention and
are exposed only inside admin views — the public challenge DTO
continues to strip them (see [Challenges Endpoints](/api/challenges.md)).
# What the `up()` does
1. **Idempotency guard**`SELECT COUNT(*) FROM "challenge"`. If the
table already contains rows the migration returns immediately; an
admin that imported a custom challenge set first is never
overwritten.
2. **System-category lookup**
`SELECT id, system_key FROM "category" WHERE system_key IS NOT NULL`
and builds an in-memory `Map<system_key, id>`. If fewer than six
canonical keys are present (the seed cannot bind) the migration
aborts as a no-op rather than inserting challenges into the wrong
category.
3. **Insert sample rows** — for each entry in `SAMPLE_CHALLENGES`, an
`INSERT INTO "challenge"` with a fresh UUID v4, ISO 8601
`created_at` / `updated_at` via `strftime('%Y-%m-%dT%H:%M:%fZ','now')`,
and `enabled=1`. Categories without a matching `system_key` are
silently skipped (defensive — only happens if the canonical six
are missing).
# Idempotency
* Running the migration twice (or restarting after it has been
recorded) leaves the same eight rows. The early-return count
guard skips the second run.
* Re-running the migration after a custom `challenge` row exists also
no-ops, so manually-imported content from the
[Admin — Challenges Full-Replace Import](/guides/admin-challenges-import.md)
flow is preserved.
# `down()`
Scoped to the seeded set: `DELETE FROM "challenge" WHERE "name" IN (...)`
using the eight fixed names from `SAMPLE_CHALLENGES`. It deliberately
matches **only** the sample names so unrelated admin rows are not
removed.
# Tests
| File | What it asserts |
|---|---|
| `tests/backend/migrations.spec.ts`*"seeds sample challenges on a fresh database (Job 906)"* | After all migrations run on an in-memory SQLite DB, the eight expected sample names are present. |
| `tests/backend/migrations.spec.ts`*"seeded sample challenges are enabled, schema-compatible, and point at the right categories (Job 906)"* | Every seeded row has `enabled=true`, a valid `difficulty`, `initial_points >= minimum_points`, a non-empty `flag`, and a `category_id` that resolves to one of the six canonical system categories. |
| `tests/backend/migrations.spec.ts`*"SeedSampleChallenges down() scope (Job 906)"* | `down()` removes only rows whose name is in the seed list and leaves manually-inserted rows of similar names (e.g. `Other`) intact. |
| `tests/backend/challenges-board.spec.ts` | Board-listing test now allows extra seeded cards inside the CRY column (`Alpha Cipher`, `Beta Cipher`, `Zeta Key`) while still asserting that the hand-inserted `alphacipher` LOW and `Zeta-key` HIGH rows occupy the bookended slots and that the disabled `hidden` row is excluded. |
# Operational notes
* On a fresh database the seed fires before the first app boot, so
[`GET /api/v1/challenges/board`](/api/challenges.md) returns the
sample columns immediately and the player-facing [Challenges Board](/guides/challenges-board.md)
shows eight cards. An admin deleting every challenge is the only
way to reach an empty board.
* To confirm on the command line after `npm run setup`:
```
sqlite3 ./data/db.sqlite 'SELECT COUNT(*) FROM challenge;'
```
Expected count: `8`. Re-running setup on a populated DB leaves the
count unchanged (idempotent guard).
* Played flags are usable for end-to-end demos: e.g. submitting
`flag{alpha}` against the Alpha Cipher card immediately awards
points and broadcasts the solve on the
[Scoreboard Stream](/guides/scoreboard-stream.md).
# See also
* [Database Schema Overview](/database/schema.md) — full migration list
and bootstrap behavior.
* [Challenge Tables](/database/challenges.md) — `challenge` schema,
indexes, and provenance.
* [Challenges Endpoints](/api/challenges.md) — board query and flag
submission.
* [Challenges Board](/guides/challenges-board.md) — user-facing guide.
* [Admin — Challenges](/guides/admin-challenges.md) — how an admin
replaces or extends the sample set.
* [Category Repair Migration](/database/category-repair-migration.md)
— earlier canonical-six migration this seed depends on.