refactor(api): static feature modules, one clock and per-kind minters, an account/ layer #536

Merged
root merged 10 commits from spec-421-api-wiring into main 2026-10-15 18:59:49 +00:00
root commented 2026-10-15 16:04:38 +00:00 (Migrated from gitlab.core-project.space)

Summary

The API's wiring now follows ADR 0044. The root module alone is dynamic and global: it provides each feature's options, one clock and one minter per kind of identity. Feature modules are static and declare what they import. Tests replace providers by token through createApp(options, overrides?).

-AppModule.forRoot(options)
-  mail  = MailModule.forRoot(options.mail)
-  auth  = AuthModule.forRoot({ ...options.auth, now, randomBytes, mail })
-  games = GamesModule.forRoot({ now, randomBytes, auth })
-  RoomModule.forRoot({ now, random, games, guests })
-  GuestModule (global), OrpcModule in every feature
+AppModule.forRoot(options, overrides)   # global: option tokens, CLOCK, minters
+  RoomModule    imports GamesModule
+  GamesModule   imports AuthModule
+  AuthModule    imports MailModule
+  AccountModule imports AuthModule, GamesModule
+  DatabaseModule (@Global), OrpcModule once, DevtoolsModule only when enabled
 apps/api/src/
 ├── common/
-│   ├── guest.service.ts, guest.module.ts
+│   ├── guest.ts            # occupant types, occupantOf, sameOccupant, Guest minter
+│   ├── time.ts             # CLOCK
+│   ├── token-hash.ts       # + secret minter
-│   ├── rate-limit.service.ts
-│   └── doorbell.ts
 ├── config/config.ts        # + rate-limit types and defaults; no Nest
 ├── auth/
+│   ├── rate-limit.service.ts
+│   └── session-cookies.ts  # every session cookie header
 ├── games/                  # + Game-id minter, exportOf
+├── account/                # account.me, account.export, account.delete
 └── room/
+    └── doorbell.ts         # + Room-code minter

Rules first, in their own commit: ADR 0044 (0043 is taken by !534) takes over the API part of ADR 0026, ADR 0027 is corrected, and the API standards' preamble and rules 1, 2 and 6 are rewritten.

Decisions taken during the work:

  • AppModule itself is global and exports the option tokens. A separate root OptionsModule class failed no-extraneous-class and max-classes-per-file.
  • account/ has no service. GamesService.exportOf gives account.export one Games call.
  • ROOM_OPTIONS and GAMES_SERVICE_OPTIONS are gone: once the clock and minters left them, they held nothing.

Evidence

  • Before: 829 API tests. Each feature set its own clock. The profiles test ran a moving Room clock beside a fixed Account clock.
    After: 834 API tests, all five gate commands green on Node 24. New cases:
rpc-clock: one CLOCK override
  Room evicted after 30 min idle, not before         -> ROOM_NOT_FOUND
  Session refused at refresh after 30 days, not before
rpc-games: a forced Game-id collision
  two Guests minted beside it stay distinct
framework-imports lint test
  config/, common/guest.ts, room/doorbell.ts framework-free
  account/*.module.ts and *.controller.ts may import Nest; account/*.service.ts may not

The profiles test now advances 1 s per Game, so its 22 Games stay inside one access token on the shared clock. Its assertions are unchanged.

Merge Danger

Door: two-way

No schema, contract or stored data changes. A revert restores the old wiring.

Blast Radius: API-boot

A provider missing from the root would fail at boot, not at a call. The whole suite boots the real app through createApp, and account.* keeps its contract.

Closes #421
Closes #422
Closes #423
Closes #424
Closes #425
Closes #426
Closes #427
Closes #428

## Summary The API's wiring now follows ADR 0044. The root module alone is dynamic and global: it provides each feature's options, one clock and one minter per kind of identity. Feature modules are static and declare what they import. Tests replace providers by token through `createApp(options, overrides?)`. ```diff -AppModule.forRoot(options) - mail = MailModule.forRoot(options.mail) - auth = AuthModule.forRoot({ ...options.auth, now, randomBytes, mail }) - games = GamesModule.forRoot({ now, randomBytes, auth }) - RoomModule.forRoot({ now, random, games, guests }) - GuestModule (global), OrpcModule in every feature +AppModule.forRoot(options, overrides) # global: option tokens, CLOCK, minters + RoomModule imports GamesModule + GamesModule imports AuthModule + AuthModule imports MailModule + AccountModule imports AuthModule, GamesModule + DatabaseModule (@Global), OrpcModule once, DevtoolsModule only when enabled ``` ```diff apps/api/src/ ├── common/ -│ ├── guest.service.ts, guest.module.ts +│ ├── guest.ts # occupant types, occupantOf, sameOccupant, Guest minter +│ ├── time.ts # CLOCK +│ ├── token-hash.ts # + secret minter -│ ├── rate-limit.service.ts -│ └── doorbell.ts ├── config/config.ts # + rate-limit types and defaults; no Nest ├── auth/ +│ ├── rate-limit.service.ts +│ └── session-cookies.ts # every session cookie header ├── games/ # + Game-id minter, exportOf +├── account/ # account.me, account.export, account.delete └── room/ + └── doorbell.ts # + Room-code minter ``` Rules first, in their own commit: ADR 0044 (0043 is taken by !534) takes over the API part of ADR 0026, ADR 0027 is corrected, and the API standards' preamble and rules 1, 2 and 6 are rewritten. Decisions taken during the work: - `AppModule` itself is global and exports the option tokens. A separate root `OptionsModule` class failed `no-extraneous-class` and `max-classes-per-file`. - `account/` has no service. `GamesService.exportOf` gives `account.export` one Games call. - `ROOM_OPTIONS` and `GAMES_SERVICE_OPTIONS` are gone: once the clock and minters left them, they held nothing. ## Evidence - **Before:** 829 API tests. Each feature set its own clock. The profiles test ran a moving Room clock beside a fixed Account clock. **After:** 834 API tests, all five gate commands green on Node 24. New cases: ```text rpc-clock: one CLOCK override Room evicted after 30 min idle, not before -> ROOM_NOT_FOUND Session refused at refresh after 30 days, not before rpc-games: a forced Game-id collision two Guests minted beside it stay distinct framework-imports lint test config/, common/guest.ts, room/doorbell.ts framework-free account/*.module.ts and *.controller.ts may import Nest; account/*.service.ts may not ``` The profiles test now advances 1 s per Game, so its 22 Games stay inside one access token on the shared clock. Its assertions are unchanged. ## Merge Danger **Door:** two-way No schema, contract or stored data changes. A revert restores the old wiring. **Blast Radius:** API-boot A provider missing from the root would fail at boot, not at a call. The whole suite boots the real app through `createApp`, and `account.*` keeps its contract. Closes #421 Closes #422 Closes #423 Closes #424 Closes #425 Closes #426 Closes #427 Closes #428
root commented 2026-10-15 16:07:38 +00:00 (Migrated from gitlab.core-project.space)

added 18 commits

  • 817ef0d4...f9f53ad5 - 8 commits from branch main
  • b40e298d - docs(api): state static modules, one clock and per-kind minters in ADR 0044
  • 56356d9d - refactor(api): move the rate limiter to auth, its defaults to config and the doorbell to room
  • 2d993c3f - refactor(api): make Guest plain functions and import the oRPC module once
  • 6a3a6992 - refactor(api): make feature modules static and override providers by token
  • 9f47a988 - refactor(api): answer account.me, account.export and account.delete from an account layer
  • 122fc4ff - refactor(api): run the whole app on one injected clock
  • 27a25216 - refactor(api): mint each kind of identity through its own injected minter
  • 52f99ce9 - docs(repo): point the ADR index and the API standards at the new layout
  • 658857e3 - refactor(api): declare the Database module global and keep session cookie headers in one file
  • 73882500 - test(api): show a Room's eviction and a Session's expiry following the one clock

Compare with previous version

added 18 commits <ul><li>817ef0d4...f9f53ad5 - 8 commits from branch <code>main</code></li><li>b40e298d - docs(api): state static modules, one clock and per-kind minters in ADR 0044</li><li>56356d9d - refactor(api): move the rate limiter to auth, its defaults to config and the doorbell to room</li><li>2d993c3f - refactor(api): make Guest plain functions and import the oRPC module once</li><li>6a3a6992 - refactor(api): make feature modules static and override providers by token</li><li>9f47a988 - refactor(api): answer account.me, account.export and account.delete from an account layer</li><li>122fc4ff - refactor(api): run the whole app on one injected clock</li><li>27a25216 - refactor(api): mint each kind of identity through its own injected minter</li><li>52f99ce9 - docs(repo): point the ADR index and the API standards at the new layout</li><li>658857e3 - refactor(api): declare the Database module global and keep session cookie headers in one file</li><li>73882500 - test(api): show a Room&#39;s eviction and a Session&#39;s expiry following the one clock</li></ul> [Compare with previous version](/amaurycoudr/chess-project/-/merge_requests/36/diffs?diff_id=240&start_sha=817ef0d477ce9f2b918c2265510cb71c5f3b1ce2)
project_1_bot_a8dd9262857d850eef8dca5206e61de5 commented 2026-10-15 16:13:45 +00:00 (Migrated from gitlab.core-project.space)

Screenshots

Before is production, after is this merge request's review app at 7388250.

Desktop

Page Before After
Home
Local game
Room

Mobile

Page Before After
Home
Local game
Room
<!-- chess-screenshots --> ### Screenshots Before is production, after is this merge request's review app at 7388250. #### Desktop | Page | Before | After | | --- | --- | --- | | Home | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1406/artifacts/raw/review-screenshots/before/home-desktop.png" width="400"> | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1406/artifacts/raw/review-screenshots/after/home-desktop.png" width="400"> | | Local game | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1406/artifacts/raw/review-screenshots/before/local-desktop.png" width="400"> | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1406/artifacts/raw/review-screenshots/after/local-desktop.png" width="400"> | | Room | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1406/artifacts/raw/review-screenshots/before/room-desktop.png" width="400"> | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1406/artifacts/raw/review-screenshots/after/room-desktop.png" width="400"> | #### Mobile | Page | Before | After | | --- | --- | --- | | Home | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1406/artifacts/raw/review-screenshots/before/home-mobile.png" width="200"> | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1406/artifacts/raw/review-screenshots/after/home-mobile.png" width="200"> | | Local game | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1406/artifacts/raw/review-screenshots/before/local-mobile.png" width="200"> | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1406/artifacts/raw/review-screenshots/after/local-mobile.png" width="200"> | | Room | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1406/artifacts/raw/review-screenshots/before/room-mobile.png" width="200"> | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1406/artifacts/raw/review-screenshots/after/room-mobile.png" width="200"> |
root (Migrated from gitlab.core-project.space) merged commit into main 2026-10-15 18:59:49 +00:00
root commented 2026-10-15 21:18:57 +00:00 (Migrated from gitlab.core-project.space)

mentioned in commit 215cdba4c9

mentioned in commit 215cdba4c9194e91817007777bbb204e7197fb99
root commented 2026-10-15 21:19:14 +00:00 (Migrated from gitlab.core-project.space)

mentioned in merge request !537

mentioned in merge request !537
Sign in to join this conversation.
No description provided.