67 lines
5.9 KiB
Markdown
67 lines
5.9 KiB
Markdown
# API and persistence contracts
|
||
|
||
## API conventions
|
||
Versioned REST at `/api/v1`. JSON UTF-8. Zod validation for input. Consistent failures `{error:{code,message,details?}}`; use 400 for malformed, 404 for missing, 409 for incompatible/duplicate, 500 for unexpected failures. `GET /health` returns 200 JSON and DB health information without credentials.
|
||
|
||
## Endpoints M0–M4
|
||
- `GET /health` — proxy-through health.
|
||
- `GET /api/v1/catalog/species` — available species and allele catalogs.
|
||
- `POST /api/v1/phenotype/resolve` — validated genome → phenotype + rarity preview; no persistence.
|
||
- `POST /api/v1/preview` — validated genome + seed + stage → serialized SVG (or typed scene; choose one and document).
|
||
- `GET /api/v1/plants?cursor=&limit=&species=&rarity=&pigment=` — paginated saved demo plants.
|
||
- `POST /api/v1/plants` — create demo plant using server-side validation and secure initial visual seed if omitted; no privilege to set own immutable rarity/parent claims.
|
||
- `GET /api/v1/plants/:id` — plant, phenotype, parent IDs and status.
|
||
- `POST /api/v1/breeding` — `{parentAId,parentBId,idempotencyKey}`; server validates, generates seed, applies genetics, persists child + event in a single transaction. Repeated key returns same result (not additional child).
|
||
- `GET /api/v1/breeding?limit=&cursor=` — recent events.
|
||
|
||
## Data model (first migration)
|
||
`species(id PK, name, compatibility_group, catalog_version, config_jsonb)`.
|
||
`players(id UUID PK, display_name, created_at)` — seed single sandbox player only; no auth yet.
|
||
`plants(id UUID PK, owner_id FK, species_id FK, genome_version integer, genome jsonb, mutations jsonb, visual_seed integer, renderer_version integer, rarity_policy_version integer, rarity_score integer, rarity_tier text, generation integer, parent_a_id nullable FK self, parent_b_id nullable FK self, created_at timestamptz)`.
|
||
`breeding_events(id UUID PK, owner_id FK, parent_a_id FK, parent_b_id FK, child_id UNIQUE FK, rng_seed integer, engine_version integer, mutation_events jsonb, idempotency_key unique text, created_at timestamptz)`.
|
||
`discoveries(player_id FK, signature text, first_discovered_at timestamptz, PK(player_id,signature))` optional in M4.
|
||
|
||
Indices: plants(owner_id,created_at DESC), plants(species_id,rarity_tier), breeding_events(owner_id,created_at DESC). Use enum validation in domain layer. Schema-specific migrations must be checked into repository. Seed catalog + demo player in idempotent script; sample gallery generated from seeded fixed seeds for repeatability.
|
||
|
||
## Example valid genome
|
||
```json
|
||
{
|
||
"pigment": ["P", "G"],
|
||
"leafShape": ["L", "O"],
|
||
"leafPattern": ["V", "S"],
|
||
"growthHabit": ["B", "C"],
|
||
"flowerColor": ["R", "W"],
|
||
"flowerDensity": ["H", "M"]
|
||
}
|
||
```
|
||
|
||
## Example breeding request
|
||
```json
|
||
{
|
||
"parentAId": "uuid-parent-a",
|
||
"parentBId": "uuid-parent-b",
|
||
"idempotencyKey": "uuid-user-generated-unique-request"
|
||
}
|
||
```
|
||
|
||
## Security and transaction invariants
|
||
- Only exposed service is the web reverse proxy. Proxy `/api/` and `/health` to `api:3000` over Compose internal network, not to `localhost` from inside container.
|
||
- Demo endpoints are deliberately not publicly secure without authentication. For internet deployment, **must** add auth + ownership and rate limits first; current MVP intended for localhost only.
|
||
- Sanitize SVG input by using known primitive IDs/validated hex palette values; never interpolate arbitrary string into SVG markup.
|
||
- No raw SQL concatenation; no secrets in frontend build; limit payload size.
|
||
- Idempotent breeding ensures retries cannot create duplicated child plants.
|
||
- API response and schema types must be shared where sensible; avoid circular import chains.
|
||
|
||
## M1 implemented examples
|
||
|
||
`GET /api/v1/catalog/species` returns `{catalogVersion:1,species:[...]}` with dominance, phenotype names, compatibility and mutation/rarity policy data. `POST /api/v1/phenotype/resolve` accepts `{speciesId:"velvetleaf",genome:<the six diploid locus pairs>,mutations:[],catalogVersion:1}` and returns `{phenotype,rarity}`. Unknown client fields (including rarity claims) and invalid alleles return a uniform 400 error. Shared request schemas/DTOs are in `packages/shared`; no breeding endpoint exists before M3.
|
||
|
||
`POST /api/v1/preview` accepts the resolve fields plus signed-int32 `visualSeed`, `growthStage` (`seedling`, `juvenile`, `mature`, `flowering`), `rendererVersion:1` and `background` (`transparent`, `warm`). It validates genomes server-side and returns `{rendererVersion:1,svg:"<svg ...>...</svg>"}` using the same pure renderer as the browser. It does not persist a plant or accept arbitrary SVG/scene/palette input.
|
||
|
||
## Implemented M3–M4 contracts
|
||
|
||
See `API_EXAMPLES.md` for copyable requests and all collection query parameters. The persisted plant model additionally stores `name`, `creation_source` (`designed`, `bred`, `demo`) and `rarity_reasons`; these are additive migration fields. Plant reads resolve the immutable versioned genome but return stored birth rarity and mutation outcomes. `/plants/:id/birth` returns the recorded breeding event for lineage/mutation inspection; `/collection/stats` returns actual saved collection counts by tier.
|
||
|
||
Collection filters are evaluated server-side against expressed traits using the catalog's dominance order and parameterized SQL. Sort columns/directions are chosen from a fixed allowlist. Cursor tuples use a UUID tie-breaker and preserve full timestamp precision; filter/sort context is part of the opaque cursor. Search treats user wildcard characters literally. Responses are bounded to at most 100 plants (UI pages contain 24).
|
||
|
||
Breeding uses cryptographically secure event seeds, sandbox-owner-scoped parent reads, and a per-owner/idempotency-key transaction lock. The child and event are inserted atomically. Repeated identical requests return the same committed child/event; reusing a key for another pair returns 409. Clients cannot set immutable fields or save manual mutation previews as breeding outcomes.
|