Reviewed by Opus 4.7 (independent agent, no shared context with author).
HIGH (must fix before merge)
H1 — import type on DTO classes breaks ValidationPipe (federation.controller.ts:45-51)
All six Body/Query DTO imports use import type, which TypeScript erases at compile time. NestJS's global ValidationPipe({ whitelist:true, forbidNonWhitelisted:true }) relies on reflect-metadata to inspect the class constructor's parameter types. With import type, the metatype resolves to Object, and ValidationPipe rejects every property as non-whitelisted (HTTP 400 at runtime). Unit tests miss this because they bypass Nest's DI/metatype lookup. This is the exact pattern that caused bug #436.
Fix: Change import type { CreateGrantDto, ListGrantsDto } and import type { CreatePeerKeypairDto, ... } to value imports.
MEDIUM (defer ok)
M1: CLI peer add posts { csrPem } — field name matches RedeemEnrollmentTokenDto.csrPem exactly ✓
M2: Route ordering (POST /peers/keypair vs PATCH /peers/:id/cert) — different methods, no conflict ✓
M3: privatePem briefly in heap after sealing — no logging, acceptable
M4: CLI only restricts to http/https, no private-IP SSRF block — acceptable for admin CLI
Confirmed OK
AdminGuard at class level — all 8 routes protected ✓
EnrollmentController's POST /api/federation/enrollment/:token not duplicated ✓
No any, no hardcoded secrets, ESM .js imports, stack file untouched ✓
439 tests pass ✓
Verdict: REQUEST_CHANGES — fix import type → value imports on DTO lines, then APPROVE.
## Independent Code Review — REQUEST_CHANGES
Reviewed by Opus 4.7 (independent agent, no shared context with author).
### HIGH (must fix before merge)
**H1 — `import type` on DTO classes breaks ValidationPipe** (`federation.controller.ts:45-51`)
All six Body/Query DTO imports use `import type`, which TypeScript erases at compile time. NestJS's global `ValidationPipe({ whitelist:true, forbidNonWhitelisted:true })` relies on reflect-metadata to inspect the class constructor's parameter types. With `import type`, the metatype resolves to `Object`, and ValidationPipe rejects every property as non-whitelisted (HTTP 400 at runtime). Unit tests miss this because they bypass Nest's DI/metatype lookup. This is the exact pattern that caused bug #436.
**Fix**: Change `import type { CreateGrantDto, ListGrantsDto }` and `import type { CreatePeerKeypairDto, ... }` to value imports.
### MEDIUM (defer ok)
- **M1**: CLI `peer add` posts `{ csrPem }` — field name matches `RedeemEnrollmentTokenDto.csrPem` exactly ✓
- **M2**: Route ordering (POST /peers/keypair vs PATCH /peers/:id/cert) — different methods, no conflict ✓
- **M3**: privatePem briefly in heap after sealing — no logging, acceptable
- **M4**: CLI only restricts to http/https, no private-IP SSRF block — acceptable for admin CLI
### Confirmed OK
- AdminGuard at class level — all 8 routes protected ✓
- EnrollmentController's `POST /api/federation/enrollment/:token` not duplicated ✓
- `enrollmentUrl` reads `BETTER_AUTH_URL` from env ✓
- `sealClientKey(privatePem)` single-arg matches util ✓
- No `any`, no hardcoded secrets, ESM `.js` imports, stack file untouched ✓
- 439 tests pass ✓
### Verdict: **REQUEST_CHANGES** — fix import type → value imports on DTO lines, then APPROVE.
All six DTO class imports now use plain value imports (no type keyword). ValidationPipe metatype lookup will work correctly at runtime.
Gates: typecheck 38/38, test 439/439 passed.
APPROVE — ready to merge.
## Re-Review — APPROVE
**H1 (import type → value imports): RESOLVED**
All six DTO class imports now use plain value imports (no `type` keyword). ValidationPipe metatype lookup will work correctly at runtime.
Gates: typecheck 38/38, test 439/439 passed.
**APPROVE — ready to merge.**
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Implements FED-M2-08: two-part delivery of the federation admin surface.
Gateway — FederationController
apps/gateway/src/federation/federation-admin.dto.ts:CreatePeerKeypairDto,StorePeerCertDto,GenerateEnrollmentTokenDto,RevokeGrantBodyDtoapps/gateway/src/federation/federation.controller.ts:FederationControllerunder/api/admin/federationwithAdminGuardon all routesPOST /grants,GET /grants,GET /grants/:id,PATCH /grants/:id/revoke— delegate toGrantsServicePOST /grants/:id/tokens— delegates toEnrollmentService, returns{ token, expiresAt, enrollmentUrl }GET /peers— direct DB queryPOST /peers/keypair— webcrypto EC P-256 key gen +@peculiar/x509CSR +sealClientKey+ insert pending peer rowPATCH /peers/:id/cert— parses cert withX509Certificate, extracts serial/notAfter, sets state=activeapps/gateway/src/federation/federation.module.ts: registersFederationControllerCLI —
mosaic federationpackages/mosaic/src/commands/federation.ts:registerFederationCommandwith fullgrantandpeersubcommand treesgrant create/list/show/revoke/tokenpeer list/add(add runs full enrollment flow: generate keypair → submit CSR to remote → store cert)-tflag ormeta.json.adminToken--jsonflag for machine-readable outputpackages/mosaic/src/cli.ts: registers the new commandTests
apps/gateway/src/federation/__tests__/federation.controller.spec.ts: unit tests covering listGrants, createGrant, generateToken (enrollmentUrl shape), listPeersCloses #461
Test plan
pnpm typecheck— passes (38/38)pnpm lint— passes (21/21)pnpm format:check— passes (all files)pnpm test— passes (439 tests, 4 skipped integration)🤖 Generated with Claude Code
Independent Code Review — REQUEST_CHANGES
Reviewed by Opus 4.7 (independent agent, no shared context with author).
HIGH (must fix before merge)
H1 —
import typeon DTO classes breaks ValidationPipe (federation.controller.ts:45-51)All six Body/Query DTO imports use
import type, which TypeScript erases at compile time. NestJS's globalValidationPipe({ whitelist:true, forbidNonWhitelisted:true })relies on reflect-metadata to inspect the class constructor's parameter types. Withimport type, the metatype resolves toObject, and ValidationPipe rejects every property as non-whitelisted (HTTP 400 at runtime). Unit tests miss this because they bypass Nest's DI/metatype lookup. This is the exact pattern that caused bug #436.Fix: Change
import type { CreateGrantDto, ListGrantsDto }andimport type { CreatePeerKeypairDto, ... }to value imports.MEDIUM (defer ok)
peer addposts{ csrPem }— field name matchesRedeemEnrollmentTokenDto.csrPemexactly ✓Confirmed OK
POST /api/federation/enrollment/:tokennot duplicated ✓enrollmentUrlreadsBETTER_AUTH_URLfrom env ✓sealClientKey(privatePem)single-arg matches util ✓any, no hardcoded secrets, ESM.jsimports, stack file untouched ✓Verdict: REQUEST_CHANGES — fix import type → value imports on DTO lines, then APPROVE.
Re-Review — APPROVE
H1 (import type → value imports): RESOLVED
All six DTO class imports now use plain value imports (no
typekeyword). ValidationPipe metatype lookup will work correctly at runtime.Gates: typecheck 38/38, test 439/439 passed.
APPROVE — ready to merge.