Compare commits

..

6 Commits

Author SHA1 Message Date
logaritmisk 8ed747c6a7 merge: wire Spectrum seed into runtime via 'server seed' (#14)
CI / web (push) Has been cancelled
server seed subcommand (idempotent; migrates then seeds the baseline Spectrum
cataloguing vocabularies + field definitions), just seed recipe, README step.
Closes #14.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 07:18:40 +02:00
logaritmisk dd02bddb07 docs: 'just seed' recipe + README seed step (#14) 2026-06-06 00:18:11 +02:00
logaritmisk 6ebcc10405 feat(server): 'seed' subcommand wiring the Spectrum cataloguing seed (#14) 2026-06-06 00:15:19 +02:00
logaritmisk 325917a98e docs(plans): wire Spectrum seed via 'server seed' subcommand (#14)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 00:12:15 +02:00
logaritmisk d74500f901 docs(specs): wire Spectrum seed into runtime via 'server seed' (#14)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 22:55:27 +02:00
logaritmisk 7d40a2cd56 chore: use cargo-nextest as the test runner
- .config/nextest.toml: hang-timeout profile (warn 60s, kill 120s)
- justfile: 'just test' = cargo nextest run --workspace + cargo test --doc
- CLAUDE.md: refresh stale Status + Commands for the real workspace + nextest

163 tests run in ~7s (vs multi-minute serial cargo test).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 22:36:59 +02:00
9 changed files with 484 additions and 11 deletions
+11
View File
@@ -0,0 +1,11 @@
# cargo-nextest configuration. https://nexte.st/book/configuration
#
# nextest runs each test in its own process: live per-test output, and a hard
# per-test timeout so a genuinely wedged test is killed + named rather than
# stalling the whole run.
[profile.default]
# Warn at 60s, terminate a test after 2×60s = 120s. The slowest real test is a
# couple of seconds (each #[sqlx::test] provisions its own temp DB), so this
# only ever fires on an actual hang.
slow-timeout = { period = "60s", terminate-after = 2 }
+12 -7
View File
@@ -4,19 +4,24 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Status
Freshly scaffolded Rust binary crate (edition 2024). `src/main.rs` is still the `cargo new` "Hello, world!" stub and `Cargo.toml` has no dependencies yet. There is no architecture to document — update this file as real structure emerges.
Rust (edition 2024) workspace + React SPA collection-management system. Backend crates: `domain`, `db`, `api`, `auth`, `search`, `server` (axum 0.8 + sqlx/Postgres + Meilisearch). Frontend in `web/` (React 19 + Vite + pnpm). Tests need the docker-compose stack up (Postgres on **:5442**, Meilisearch on **:7700**); each `#[sqlx::test]` provisions its own temp DB.
## Commands
```bash
cargo build # build
cargo run # run the binary
cargo test # run all tests
cargo test <name> # run a single test by name substring
cargo +nightly fmt # format — always nightly, not stable
cargo clippy # lint before committing
just check # fmt + lint + test — the standard pre-commit gate
docker compose up -d # start Postgres (:5442) + Meilisearch (:7700) for tests
cargo build --workspace # build
cargo run -p server # run the server (or: just run — loads .env)
cargo nextest run --workspace # run all tests — PREFERRED (per-test isolation, live output, hang timeouts)
cargo nextest run -E 'test(<name>)' # run tests matching a name substring
cargo test --workspace --doc # doctests (nextest does not run these)
cargo +nightly fmt # format — always nightly, not stable
cargo clippy --workspace --all-targets -- -D warnings # lint before committing
```
(`just test` runs nextest + doctests; config in `.config/nextest.toml`.)
## Conventions
- **CLI args & env vars:** use `clap` with the `derive` feature.
+8 -1
View File
@@ -49,7 +49,14 @@ BOOTSTRAP_PASSWORD=changeme123 cargo run -p server -- create-user --email you@ex
```
Roles are `admin` or `editor`.
### 5. Run the web frontend
### 5. Seed the baseline cataloguing fields (idempotent)
```bash
just seed # or: cargo run -p server -- seed
```
Populates the baseline Spectrum cataloguing vocabularies and field definitions. Safe to
re-run — the seed is idempotent.
### 6. Run the web frontend
The API server serves JSON only; in development the SPA is served by Vite, which proxies
`/api` to `:8080`:
```bash
+25
View File
@@ -117,6 +117,31 @@ pub mod test_support {
}
}
/// One-shot: apply migrations (idempotent), then seed the baseline Spectrum cataloguing
/// vocabularies + field definitions. Safe to re-run (the seed is idempotent).
pub async fn seed(database_url: &str) -> anyhow::Result<()> {
// CLI one-shot: a tiny pool is plenty.
let db = Db::connect(database_url, 2)
.await
.context("connecting to the database")?;
// Apply migrations first so `server seed` works on a fresh DB without first
// starting the server. Migrations are idempotent.
db.migrate().await.context("running database migrations")?;
let mut tx = db.pool().begin().await?;
db::seed::seed_spectrum_cataloguing(&mut tx)
.await
.context("seeding Spectrum cataloguing baseline")?;
tx.commit().await?;
println!("seeded Spectrum cataloguing baseline (idempotent)");
Ok(())
}
/// Create a user from the CLI (admin bootstrap). Opens its own connection (CLI
/// one-shot); reads the password from the `BOOTSTRAP_PASSWORD` env var if set,
/// otherwise prompts (hidden input). The plaintext is not zeroized, but it is
+4 -1
View File
@@ -1,6 +1,6 @@
use clap::{Parser, Subcommand, ValueEnum};
use domain::Role;
use server::{Config, create_user, run};
use server::{Config, create_user, run, seed};
#[derive(Parser)]
#[command(version, about = "Collection management system server")]
@@ -20,6 +20,8 @@ enum Command {
#[arg(long, value_enum)]
role: RoleArg,
},
/// Seed the baseline Spectrum cataloguing vocabularies + field definitions (idempotent).
Seed,
}
#[derive(Clone, Copy, ValueEnum)]
@@ -50,5 +52,6 @@ async fn main() -> anyhow::Result<()> {
Some(Command::CreateUser { email, role }) => {
create_user(&cli.config.database_url, &email, role.into()).await
}
Some(Command::Seed) => seed(&cli.config.database_url).await,
}
}
+33
View File
@@ -0,0 +1,33 @@
use db::{Db, fields, seed, vocab};
use sqlx::PgPool;
// Note: `server::seed` opens its own DB connection by URL, but `#[sqlx::test]`
// provisions a temporary database whose URL is not directly exposed. This test
// exercises the building block the command composes — `db::seed::seed_spectrum_cataloguing`
// — against the test pool, run twice to prove the idempotency the command relies on.
#[sqlx::test(migrations = "../db/migrations")]
async fn seed_is_idempotent_via_building_block(pool: PgPool) {
let db = Db::from_pool(pool);
for _ in 0..2 {
let mut tx = db.pool().begin().await.unwrap();
seed::seed_spectrum_cataloguing(&mut tx).await.unwrap();
tx.commit().await.unwrap();
}
// A representative seeded vocabulary and field definition are present after two runs.
assert!(
vocab::vocabulary_by_key(db.pool(), "material")
.await
.unwrap()
.is_some(),
"vocabulary 'material' should be seeded"
);
assert!(
fields::field_definition_by_key(db.pool(), "title")
.await
.unwrap()
.is_some(),
"field definition 'title' should be seeded"
);
}
@@ -0,0 +1,237 @@
# Wire the Spectrum Seed into Runtime Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Expose the existing idempotent `db::seed::seed_spectrum_cataloguing` as a `server seed` CLI subcommand (plus a `just seed` recipe and README note), so an operator can seed an instance's baseline cataloguing fields.
**Architecture:** Mirror the existing `create-user` one-shot exactly — add a `Seed` variant to the clap `Command` enum, dispatch it to a new `server::seed(database_url)` that connects with a tiny pool, applies migrations (idempotent, so it works on a fresh DB), runs the seed inside a transaction, commits, and exits. The seed content and its idempotency are already tested at the db layer; the new code is thin glue.
**Tech Stack:** Rust (clap derive, sqlx/Postgres, anyhow, tokio). Backend-only + docs.
**Conventions:** `cargo +nightly fmt`; `cargo clippy --workspace --all-targets -- -D warnings`; tests via `cargo nextest run`; never write the codename ("biggus"/"dickus"). Test infra: compose Postgres on host **5442**, Meili **7700**; `#[sqlx::test(migrations = "../db/migrations")]` provisions its own temp DB. Env for manual runs comes from `.env` via the justfile's `set dotenv-load`.
**Spec:** `docs/superpowers/specs/2026-06-05-spectrum-seed-wiring-design.md`
---
## File Structure
- `crates/server/src/main.rs` — add a `Seed` variant to the `Command` enum + a dispatch arm.
- `crates/server/src/lib.rs` — add `pub async fn seed(database_url: &str) -> anyhow::Result<()>` (modeled on `create_user`, but with a `db.migrate()` step).
- `crates/server/tests/seed.rs` (new) — a server-crate building-block regression test mirroring `crates/server/tests/create_user.rs` (seed twice via the test pool; assert a known seeded vocabulary + field).
- `justfile` — add a `seed` recipe.
- `README.md` — add a seed step to the "Running locally" setup sequence.
The seed *content* + idempotency stay covered by the existing `crates/db/tests/seed.rs` (unchanged).
---
## Task 1: `server seed` subcommand
**Files:**
- Modify: `crates/server/src/main.rs`
- Modify: `crates/server/src/lib.rs`
- Create: `crates/server/tests/seed.rs`
**Reference (the template to mirror) — `server::create_user` in `crates/server/src/lib.rs`:**
```rust
pub async fn create_user(database_url: &str, email: &str, role: Role) -> anyhow::Result<()> {
// ...email parse + password hash...
let db = Db::connect(database_url, 2).await.context("connecting to the database")?;
let mut tx = db.pool().begin().await?;
let id = db::users::create_user(&mut tx, AuditActor::System, &NewUser { /* ... */ }).await
.context("creating the user (is the email already taken?)")?;
tx.commit().await?;
println!("created user {id} ({role:?})");
Ok(())
}
```
`Db::connect(url, n)`, `db.migrate()`, `db.pool()` all already exist (`run` calls `db.migrate()` at `lib.rs:22`). The seed fn `db::seed::seed_spectrum_cataloguing(conn: &mut sqlx::PgConnection)` is idempotent and uses `AuditActor::System` internally — no actor plumbing needed.
- [ ] **Step 1: Write the server-crate building-block test.** Create `crates/server/tests/seed.rs`. Mirror the harness comment + pool approach from `crates/server/tests/create_user.rs` (the temp-DB URL isn't exposed, so we exercise the building block the command composes — `db::seed::seed_spectrum_cataloguing` — against the test pool, including a second run to prove idempotency):
```rust
use db::{Db, fields, seed, vocab};
use sqlx::PgPool;
// Note: `server::seed` opens its own DB connection by URL, but `#[sqlx::test]`
// provisions a temporary database whose URL is not directly exposed. This test
// exercises the building block the command composes — `db::seed::seed_spectrum_cataloguing`
// — against the test pool, run twice to prove the idempotency the command relies on.
#[sqlx::test(migrations = "../db/migrations")]
async fn seed_is_idempotent_via_building_block(pool: PgPool) {
let db = Db::from_pool(pool);
for _ in 0..2 {
let mut tx = db.pool().begin().await.unwrap();
seed::seed_spectrum_cataloguing(&mut tx).await.unwrap();
tx.commit().await.unwrap();
}
// A representative seeded vocabulary and field definition are present after two runs.
assert!(
vocab::vocabulary_by_key(db.pool(), "material").await.unwrap().is_some(),
"vocabulary 'material' should be seeded"
);
assert!(
fields::field_definition_by_key(db.pool(), "title").await.unwrap().is_some(),
"field definition 'title' should be seeded"
);
}
```
(Confirm the seeded keys by reading `crates/db/src/seed.rs` — it seeds vocabularies `material`/`object_name`/`technique` and a field def `title`; adjust the asserted keys if they differ.)
- [ ] **Step 2: Run the test — it should PASS immediately.**
```
DATABASE_URL=postgres://postgres:postgres@localhost:5442/cms_dev cargo nextest run -p server -E 'test(seed_is_idempotent_via_building_block)'
```
Expected: PASS. (Unlike classic TDD, this guards an already-working building block the new command depends on — there is no failing-first state because `db::seed` already exists. The genuinely new code is the glue in Steps 34, verified by build + the manual smoke in Step 6.)
- [ ] **Step 3: Add the `Seed` command variant + dispatch** in `crates/server/src/main.rs`. Add to the `Command` enum (after `CreateUser { … }`):
```rust
/// Seed the baseline Spectrum cataloguing vocabularies + field definitions (idempotent).
Seed,
```
And add a match arm in `main` (the `match cli.command { … }`), after the `CreateUser` arm:
```rust
Some(Command::Seed) => seed(&cli.config.database_url).await,
```
Update the import at the top of `main.rs` from `use server::{Config, create_user, run};` to:
```rust
use server::{Config, create_user, run, seed};
```
- [ ] **Step 4: Add the `seed` one-shot** in `crates/server/src/lib.rs`, next to `create_user`:
```rust
/// One-shot: apply migrations (idempotent), then seed the baseline Spectrum cataloguing
/// vocabularies + field definitions. Safe to re-run (the seed is idempotent).
pub async fn seed(database_url: &str) -> anyhow::Result<()> {
// CLI one-shot: a tiny pool is plenty.
let db = Db::connect(database_url, 2)
.await
.context("connecting to the database")?;
// Apply migrations first so `server seed` works on a fresh DB without first
// starting the server. Migrations are idempotent.
db.migrate()
.await
.context("running database migrations")?;
let mut tx = db.pool().begin().await?;
db::seed::seed_spectrum_cataloguing(&mut tx)
.await
.context("seeding Spectrum cataloguing baseline")?;
tx.commit().await?;
println!("seeded Spectrum cataloguing baseline (idempotent)");
Ok(())
}
```
(`Db`, `anyhow::Context`/`context` are already imported in `lib.rs` — verify the `use` lines; `create_user` already uses `.context(...)` and `Db::connect`, so the imports exist.)
- [ ] **Step 5: Build, fmt, clippy, and run the server tests.**
```
cargo +nightly fmt
cargo clippy --workspace --all-targets -- -D warnings
DATABASE_URL=postgres://postgres:postgres@localhost:5442/cms_dev cargo nextest run -p server
```
Expected: builds clean, clippy clean, all server tests pass (including the existing `create_user` + `config` + `serve` + `embed` tests and the new seed test). Also confirm the subcommand is wired:
```
cargo run -p server -- --help
```
Expected: the help output lists a `seed` subcommand alongside `create-user`.
- [ ] **Step 6: Manual smoke — verify the real command (connect + migrate + commit glue).** With compose up (`docker compose up -d`):
```
DATABASE_URL=postgres://postgres:postgres@localhost:5442/cms_dev cargo run -p server -- seed
DATABASE_URL=postgres://postgres:postgres@localhost:5442/cms_dev cargo run -p server -- seed
```
Expected: both print `seeded Spectrum cataloguing baseline (idempotent)` and exit 0 (the second run is a no-op). (This exercises the URL-connect + migrate + commit path that `#[sqlx::test]` can't.)
- [ ] **Step 7: Commit.**
```bash
git add crates/server
git commit -m "feat(server): 'seed' subcommand wiring the Spectrum cataloguing seed (#14)"
```
---
## Task 2: `just seed` recipe + README note
**Files:**
- Modify: `justfile`
- Modify: `README.md`
- [ ] **Step 1: Add the `seed` recipe** to `justfile`. Insert after the `run` recipe (keeping the existing comment style), before `test`:
```
# Seed the baseline Spectrum cataloguing vocabularies + field definitions (idempotent)
seed:
cargo run -p server -- seed
```
- [ ] **Step 2: Verify just parses it.**
```
just --list
```
Expected: `seed` appears in the recipe list with its description.
- [ ] **Step 3: Add a seed step to the README "Running locally" setup sequence.** Open `README.md`, find the "Running locally" section and the step that creates the admin user (the `create-user` instruction). Immediately after it, add a step:
```markdown
4. Seed the baseline cataloguing fields (idempotent):
```bash
just seed # or: cargo run -p server -- seed
```
```
(Match the surrounding numbering/formatting of the existing steps — renumber subsequent steps if the section is numbered. Read the section first and adapt the wording to its style; the content is: run `just seed` once after creating the admin user to populate the baseline Spectrum vocabularies + field definitions.)
- [ ] **Step 4: Commit.**
```bash
git add justfile README.md
git commit -m "docs: 'just seed' recipe + README seed step (#14)"
```
---
## Task 3: Final verification
**Files:** none (verification only).
- [ ] **Step 1: Full suite + lints.**
```
cargo +nightly fmt --check
cargo clippy --workspace --all-targets -- -D warnings
DATABASE_URL=postgres://postgres:postgres@localhost:5442/cms_dev MEILI_URL=http://localhost:7700 MEILI_MASTER_KEY=masterKey cargo nextest run --workspace
```
Expected: all green.
- [ ] **Step 2: Codename scan + tree hygiene.**
```
git grep -in 'biggus\|dickus' -- crates README.md justfile || echo "CLEAN"
git status --short
```
Expected: `CLEAN`; working tree clean after the task commits.
---
## Self-Review (completed)
**1. Spec coverage:**
- `server seed` subcommand → Task 1 (main.rs variant + dispatch). ✓
- `server::seed` one-shot mirroring create_user, migrate-first → Task 1 Step 4. ✓
- Idempotent / safe to re-run → asserted in Task 1 Step 1 test + Step 6 smoke. ✓
- `just seed` recipe + README note → Task 2. ✓
- Testing: existing db-layer seed tests unchanged + new server-crate building-block test + manual glue smoke → Task 1. ✓
- Acceptance: nextest green / fmt / clippy / no codename → Task 3. ✓
- Out of scope (no `--seed` flag, no auto-boot, no provisioning, no term seeding, create_user unchanged) → respected; only the four files above change. ✓
**2. Placeholder scan:** No TBD/“handle errors”/“similar to”. The two “confirm the seeded keys / read the section first” notes are verification steps against real files, not deferred implementation; concrete code is given for every code step.
**3. Type consistency:** `seed(database_url: &str) -> anyhow::Result<()>` is defined in Task 1 Step 4 and imported/dispatched in Step 3 (`use server::{… seed}`, `Some(Command::Seed) => seed(&cli.config.database_url).await`). The test uses `db::seed::seed_spectrum_cataloguing(&mut tx)` + `vocab::vocabulary_by_key` + `fields::field_definition_by_key`, all existing signatures (mirrored from `crates/db/tests/seed.rs` and `create_user.rs`).
## Notes
- No new dependencies → no `Cargo.lock` churn expected.
- `Command::Seed` has no clap args; it reuses the flattened `Config.database_url`, exactly like `CreateUser` does.
@@ -0,0 +1,146 @@
# Wire the Spectrum Seed into Runtime — Design
**Date:** 2026-06-05
**Status:** Approved (brainstorming) — ready for implementation planning.
**Issue:** #14.
## Context
`db::seed::seed_spectrum_cataloguing(conn)` already exists, is **idempotent** (each
vocabulary/field-definition is created only if its key is absent), and is tested at the
db layer (`crates/db/tests/seed.rs` covers content + re-seed idempotency). Nothing
invokes it yet — #14 is purely about **wiring**.
The server binary already has the pattern to extend. `crates/server/src/main.rs` defines
a clap `Command` enum with one variant (`CreateUser`); `main` dispatches `None → run`,
`Some(sub) → one-shot`. `server::create_user(database_url, …)` (`crates/server/src/lib.rs`)
is the one-shot template: connect with a tiny pool (`Db::connect(url, 2)`), open a
transaction, do the work with `AuditActor::System`, commit, print/log, exit.
The app is **single-tenant** (env-driven config, no control plane / provisioning service).
So #14's suggested "per-org provisioning" home does not exist yet; the realistic wiring
now is a manual one-shot, mirroring `create-user`.
### Decision (from brainstorming)
A **`server seed` CLI subcommand** — explicit, idempotent, safe to re-run, no coupling to
the serve path. (Rejected: a `--seed` startup flag — couples seeding to serving;
auto-seed-on-first-boot — silently mutates data on boot, needs first-boot detection; the
provisioning path — no control plane exists.) The operator runs `server seed` once when
setting up an instance, alongside `server create-user`.
## Components
### 1. `Command::Seed` variant (`crates/server/src/main.rs`)
Add to the `Command` enum:
```rust
/// Seed the baseline Spectrum cataloguing vocabularies + field definitions (idempotent).
Seed,
```
And a dispatch arm in `main`:
```rust
Some(Command::Seed) => seed(&cli.config.database_url).await,
```
`Seed` takes no args of its own — it uses the flattened `Config`'s `database_url` (which
already reads `DATABASE_URL` from env / `--database-url`), exactly like `CreateUser` reads
`cli.config.database_url`.
### 2. `server::seed` one-shot (`crates/server/src/lib.rs`)
A new public function modeled on `create_user`:
```rust
/// One-shot: apply migrations (idempotent) then seed the baseline Spectrum cataloguing
/// vocabularies + field definitions. Safe to re-run.
pub async fn seed(database_url: &str) -> anyhow::Result<()> {
let db = Db::connect(database_url, 2)
.await
.context("connecting to the database")?;
// Apply migrations first so `server seed` works on a fresh DB without first
// starting the server. Migrations are idempotent. (This is a deliberate, robust
// step beyond create_user, which assumes a migrated DB.)
db.migrate().await.context("running database migrations")?;
let mut tx = db.pool().begin().await?;
db::seed::seed_spectrum_cataloguing(&mut tx)
.await
.context("seeding Spectrum cataloguing baseline")?;
tx.commit().await?;
println!("seeded Spectrum cataloguing baseline (idempotent)");
Ok(())
}
```
Notes:
- The seed function uses `AuditActor::System` internally for the vocabulary creates, so no
actor plumbing is needed at the server layer.
- It returns `()`; the printed line is a generic confirmation (re-running a fully-seeded DB
prints the same line — correct, since the operation is idempotent).
- `Db::migrate()` is the same method `run` calls on startup (`lib.rs:22`).
### 3. Convenience: `just seed` recipe + README note
- `justfile`: add
```
# Seed the baseline Spectrum cataloguing vocabularies + field definitions (idempotent)
seed:
cargo run -p server -- seed
```
(`set dotenv-load` already supplies `DATABASE_URL`.)
- `README.md` "Running locally": add one line to the setup steps, e.g. after creating the
admin user — "Seed the baseline cataloguing fields: `just seed` (or
`cargo run -p server -- seed`)."
## Data flow
`server seed` → `server::seed(database_url)` → `Db::connect` → `db.migrate()` →
`tx = begin()` → `db::seed::seed_spectrum_cataloguing(&mut tx)` (idempotent ensure-by-key)
→ `tx.commit()` → confirmation line → exit 0.
## Error handling
- Connection / migration failures → `anyhow` error with context, non-zero exit (matches
`create_user`).
- A partial seed cannot persist: all inserts run inside the single transaction, so any
error rolls the whole seed back (the seed fn already takes `&mut *tx`).
- Re-running on an already-seeded DB is a no-op (ensure-by-key) and exits 0.
## Testing
- **Existing (db layer):** `crates/db/tests/seed.rs` already asserts the seed creates the
expected vocabularies + field definitions and that re-seeding is idempotent. No change.
- **New (server layer):** add a test mirroring `crates/server/tests/create_user.rs`'s
harness (read it to match how it bridges a `#[sqlx::test]` `PgPool` to the
`database_url`-taking one-shot). The test exercises the wiring end-to-end: invoke the
seed one-shot **twice** against a fresh test DB and assert it succeeds both times and
that a known seeded row (e.g. vocabulary `material`, field definition `title`) is
present. This proves the `seed` glue + migrate path, complementing the db-layer content
tests.
- If `create_user.rs` tests the db layer directly rather than `server::create_user`
(because the one-shot takes a URL, not a pool), mirror that: call
`db::seed::seed_spectrum_cataloguing` twice via the pool and assert idempotent success.
The thin `server::seed` glue (connect + migrate + commit) is then covered by manual
verification (below).
- **Manual verification:** `docker compose up -d`, then `just seed` twice — both exit 0;
the second is a no-op; the seeded vocabularies/fields appear in the `/vocabularies` and
`/fields` admin screens.
## Acceptance criteria
1. `server seed` (and `just seed`) applies the idempotent Spectrum cataloguing seed and
exits 0; re-running is a safe no-op.
2. It works on a fresh (but reachable) database — migrations are applied first.
3. The wiring mirrors the existing `create-user` one-shot (pool of 2, tx, `AuditActor::System`
via the seed fn, `anyhow` context on failure).
4. `cargo nextest run --workspace` green; `cargo +nightly fmt` + `clippy -D warnings` clean;
no codename.
5. README "Running locally" documents the seed step; `just seed` recipe present.
## Out of scope
- `--seed` startup flag; auto seed-on-first-boot.
- Per-org provisioning / control-plane seeding (no control plane exists; revisit if it lands).
- Seeding vocabulary **terms** (the seed deliberately creates vocabularies empty; terms are
populated by the organisation or a later import).
- Making `create-user` migrate (out of scope; only `seed` gains the migrate step here).
+8 -2
View File
@@ -4,9 +4,15 @@ set dotenv-load
run:
cargo run -p server
# Run the full test suite
# Seed the baseline Spectrum cataloguing vocabularies + field definitions (idempotent)
seed:
cargo run -p server -- seed
# Run the full test suite via cargo-nextest (per-test isolation, live output,
# hang timeouts). nextest does not run doctests, so they run separately.
test:
cargo test --workspace
cargo nextest run --workspace
cargo test --workspace --doc
# Format with the nightly toolchain
fmt: