5.1 KiB
Implementation Plan: First-Start Create Admin Modal 1.06 — Bootstrap Race Fix
0. Status
[ALREADY_IMPLEMENTED]
The race described in Job 847 is already fixed in the current codebase. Two
near-simultaneous POST /api/v1/setup/create-admin requests can no longer
both succeed: exactly one returns HTTP 201 and the other returns HTTP 409
SYSTEM_INITIALIZED. No new code or test work is required.
1. Architectural Reconnaissance
- Codebase style & conventions: TypeScript, NestJS 10+ (modular, DI,
controllers + services), class-based services, TypeORM with the
DataSource.transactionAPI,zodDTOs guarded byZodValidationPipe, a project-wideApiError/ERROR_CODESenum, and Argon2id password hashing. - Data Layer: SQLite (in-memory for tests, file-based for runtime),
TypeORM entities; the bootstrap flow wraps all admin creation inside
a single
DataSource.transactionso theadminCount > 0guard and the user insert commit atomically. - Test Framework & Structure: Jest. Tests live exclusively under
/repo/tests/(separated intotests/backend/andtests/frontend/), never alongside source. Backed by acsrfClienthelper intests/backend/csrf-client.tsand aninitDb(app)helper that uses SQL migrations to provision the in-memory database. Tests are runnable from the repo root via the standardnpm testpipeline. - Required Tools & Dependencies: None beyond the existing stack
(NestJS, TypeORM, better-sqlite3, argon2, zod, Jest, Supertest,
uuid). No new package, system tool, or
setup.shchange is needed.
2. Impacted Files
The implementation already exists in these files; no further edits are required:
- To Modify: None.
- To Create: None.
- Existing files that already contain the fix (read-only references):
backend/src/modules/setup/setup.service.ts:23,39-97,111-130—SetupService.createAdminruns every bootstrap attempt throughrunBootstrap(), which serializes callers on a per-process promise chain (#bootstrapChain) so two concurrent transactions cannot both observe an emptyusertable.backend/src/modules/setup/setup.service.ts:60-95— Each serialized call executes insidedataSource.transaction(...), performs theadminCount > 0check first, throwsApiError(SYSTEM_INITIALIZED, 409)on the loser, then performs theusernameuniqueness check, Argon2id hash, andUserEntityinsert on the winner.backend/src/modules/setup/setup.module.ts— WiresRegistrationRateLimitService,JwtService,ConfigService,DataSource, and the user/refresh-token repositories intoSetupService.backend/src/common/errors/api-error.ts,backend/src/common/errors/error-codes.ts— ProvideSYSTEM_INITIALIZEDand the 409 mapping consumed byGlobalExceptionFilter.tests/backend/setup-create-admin.spec.ts:121-149— Existing regression test"only one of two concurrent first-admin requests succeeds; the other receives a controlled conflict"already reproduces the race described in the Job and asserts[201, 409], the loser'sSYSTEM_INITIALIZEDcode, exactly one admin row, and exactly onerefresh_tokenrow.
3. Proposed Changes
No changes. The Job's expected behaviour is already enforced by two cooperating layers:
- Database / Schema Migration: None. The existing
user.roleindex plus the in-transactionadminCountcheck are sufficient. - Backend Logic & APIs:
SetupService.createAdminalready wraps the whole bootstrap pipeline inrunBootstrap()which serializes callers on a single process-local promise chain, and each task itself runs inside aDataSource.transactionwhose first action is theadminCount > 0guard. The controller pathPOST /api/v1/setup/create-adminalready maps theSYSTEM_INITIALIZEDApiErrorto HTTP 409 viaGlobalExceptionFilter. - Frontend UI Integration: No change needed. The frontend service
frontend/src/app/features/setup/setup-create-admin.service.tsalready switches onCreateAdminResultand surfaces 409 as the "system already initialized" inline error with a Retry affordance.
4. Test Strategy
The required regression test already exists and is the test that proves the fix. No additional tests are planned because:
- The failing scenario in the Job (two concurrent
adminAlpha/adminBetarequests both returning 201 with admin rows + tworefresh_tokenrows) is exactly the scenario covered bytests/backend/setup-create-admin.spec.ts:121-149. - Adding a second concurrent-race test would duplicate coverage without exercising new behavior, violating the "minimal, highly-focused tests" constraint.
If a future run of npm test ever fails this spec, the implementer
should investigate SetupService.runBootstrap and the transactional
adminCount guard (not add tests).
Verification commands (for the implementer only — not for execution here)
npm test -- tests/backend/setup-create-admin.spec.ts— confirms 201/409 outcome, single admin row, singlerefresh_tokenrow.npm test— full Jest run from the repo root.