mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
docs: regenerate migration-graph after the notifications fold
Import-scan regeneration: notificationService and the folded inAppNotifications leave the graph, the notifications cluster is consumed as plain infra by the DI service, and the unblock cascade over-delivered — repointing memories' sends erased that cluster's last domain edge, so BOTH adminService and journeyService join the ready frontier (adminService heads the order). Adds the dated correction bullet and the 'Quirks fixed after the notifications fold' section (fixed vs deliberately-preserved).
This commit is contained in:
+199
-57
@@ -1,9 +1,53 @@
|
||||
# Legacy `src/services/` dependency graph
|
||||
|
||||
Generated from the actual imports in `server/src` on **2026-08-02** (after the
|
||||
notificationService fold — the notifications fan-in, step 3 of the
|
||||
dependency-honest order, cashed in at last after being deferred through the
|
||||
whole step-4 chain: the 354-line `send()` dispatcher and the 371-line
|
||||
`inAppNotifications.ts` in-app store folded **together** into the existing
|
||||
thin wrapper `nest/notifications/notifications.service.ts`
|
||||
(`NotificationsService`) over `DatabaseService` + injected `RealtimeService` —
|
||||
the wrapper's in-app delegation became the real SQL (identical statements,
|
||||
the three divergent `toUtcIso` treatments, the action-before-CAS respond
|
||||
flow), and `send()` arrived with its event-config map, gating and log lines
|
||||
verbatim. The notifications *cluster* did NOT fold: the preference matrix,
|
||||
the smtp/webhook/ntfy transports, `notifications/{channelRegistry,builtins}`
|
||||
(their registry⇄prefs cycle included) and `inAppNotificationActions` stay
|
||||
plain infra modules per the classification below — the fold consumes the
|
||||
cluster's seams as plain imports, exactly like tripAccess/webauthnConfig
|
||||
before them. The bridge tax — the heaviest of any remaining fold — landed as
|
||||
ONE file: the 1-export `notifications.bridge.ts` (`send`) serves the
|
||||
scheduler's two cron `require()`s, the legacy adminService +
|
||||
memories/{unified,synology} senders, and the six deliberately-lazy
|
||||
fire-and-forget `import().then(({ send }) => …)` sends inside migrated Nest
|
||||
services (collab/collections/packing/reservations/trips/vacay — path-only
|
||||
repoints that keep the lazy shape so a send can never block or cycle a
|
||||
domain module). In-container static consumers inject instead:
|
||||
`AdminController`'s dev test-notification send (AdminModule →
|
||||
NotificationsModule) and the plugin RPC host, where
|
||||
`sendPluginNotification`'s legacy import became the injected
|
||||
`NotificationsService` — the factory's 24th constructor dep, leaving
|
||||
weatherService + journeyService as its last legacy domain imports. The
|
||||
5-tool legacy registrar `mcp/tools/notifications.ts` and the
|
||||
`notifications-in-app` resource moved onto the decorator registry as
|
||||
`notifications.mcp.ts` (no `when:` — the domain is core, not addon-gated;
|
||||
the `'Notification not found.'` trailing-period divergence from REST's
|
||||
`'Not found'` is pinned, not fixed). Tests moved with IDs preserved
|
||||
(notificationService.test.ts → nest/notifications.service.test.ts,
|
||||
NSVC-001…019/NTFY-SVCB-*/NSVC-PLUG-001…007 + the new NSVC-020 bridge pin;
|
||||
inAppNotificationPrefs.test.ts → nest/notifications.inapp-prefs.test.ts,
|
||||
INOTIF-*), ~18 suites repointed their path mocks/warm-ups to the bridge
|
||||
path, admin.controller.test and the plugin-host factory suite converted
|
||||
theirs to constructor stubs, and the module e2e went DI-native (real
|
||||
notifications DDL; prefs/transports stay path-mocked). A sibling DTO ratchet
|
||||
cleared all five `NotificationsController` allow-list entries —
|
||||
`testNtfyRequestSchema` gained `.nullable()` server/token because the client
|
||||
sends null to mean "use the saved value" — trading the inline
|
||||
`response`-enum and url-type checks for the pipe envelope. **adminService is
|
||||
unblocked and heads the frontier.** Earlier the same day, the
|
||||
passkeyService fold — the second frontier cash-in of the auth fold and the
|
||||
last member of the oidc/passkey pair, while the notifications fan-in stays
|
||||
the order's official next step: the 364-line WebAuthn module folded into a
|
||||
last member of the oidc/passkey pair:
|
||||
the 364-line WebAuthn module folded into a
|
||||
**new** `nest/auth/passkey.service.ts` (`PasskeyService`) over
|
||||
`DatabaseService` + injected `AuthService` — unlike every prior fold there
|
||||
was no wrapper service to fill: the delegation shim was `PasskeyController`
|
||||
@@ -161,7 +205,7 @@ all three `AtlasController` body-contract allow-list entries
|
||||
whitespace-only bucket name keeps its legacy `'Name is required'` trim guard.
|
||||
Earlier the same day, the collectionsService fold —
|
||||
the biggest single fold yet, taken off the ready
|
||||
frontier while the notifications fan-in stays the order's official next step:
|
||||
frontier while the notifications fan-in stayed the order's official next step:
|
||||
the 1024-line saved-places core (visibility/roles, the collection-scoped
|
||||
dedup, saved-places CRUD, copy-to-trip with the ratings filters, labels, the
|
||||
fusion invitation state machine) folded into the DI-native `CollectionsService`
|
||||
@@ -195,7 +239,8 @@ container exists), its sole consumer — the in-container `transit.mcp.ts` — w
|
||||
a one-import repoint, and a new 21-case `TRANSIT-ITIN-*` characterization suite
|
||||
pins the previously untested superRefine error strings, `??` time fallbacks,
|
||||
coordinate tolerances and builder output. The
|
||||
notifications fan-in heads the frontier. Before that, the placeService
|
||||
notifications fan-in headed the frontier from there (cashed in 2026-08-02,
|
||||
above). Before that, the placeService
|
||||
migration (2026-07-30): the 1029-line place core (CRUD + ratings SQL,
|
||||
the GPX/KML/KMZ importers, the Google/Naver list importers) folded into the
|
||||
DI-native `PlacesService`; the pure pieces — the frozen XML parsers, the KMZ
|
||||
@@ -245,8 +290,9 @@ How to read it:
|
||||
`tripAccess` (Wave-2 "don't migrate, delete": absorb into `DatabaseService.canAccessTrip`).
|
||||
Edges tagged **(lazy)** are `import()`/`require()` at call time — they never block a
|
||||
migration, because a migrated Nest service can keep the same lazy import until the target
|
||||
domain migrates (collab/packing/trips/reservations/vacay all do exactly that for
|
||||
`notificationService`).
|
||||
domain migrates (collab/collections/packing/trips/reservations/vacay all did exactly that
|
||||
for `notificationService`; since its 2026-08 fold those lazy `import()`s point at
|
||||
`nest/notifications/notifications.bridge`, still lazily).
|
||||
- **imported by (services/)** — legacy files that would need a **bridge or repoint** when this
|
||||
one migrates (a legacy module can't inject).
|
||||
- **nest consumers** — in-container consumers: repoint to the injected service
|
||||
@@ -362,10 +408,20 @@ How to read it:
|
||||
`AdminService` injects it for the passkey reset via `exports:
|
||||
[PasskeyService]` + an AdminModule→AuthModule import;
|
||||
`webauthnConfig`/`stripUserForClient`/`avatarUrl` stay plain helper
|
||||
imports).
|
||||
imports),
|
||||
notifications (the `send()` dispatcher **and** the `inAppNotifications`
|
||||
in-app store folded together into the pre-existing wrapper
|
||||
`NotificationsService` over `DatabaseService` + `RealtimeService`; the
|
||||
preference matrix, transports, channel registry and
|
||||
`inAppNotificationActions` stay plain infra imports; a 1-export
|
||||
`notifications.bridge.ts` (`send`) serves scheduler + legacy
|
||||
adminService/memories + the six lazy fire-and-forget sends in migrated
|
||||
Nest services; `AdminController` and the plugin host (24th factory dep)
|
||||
inject; the 5-tool registrar + in-app resource moved to
|
||||
`notifications.mcp.ts`).
|
||||
- **Domain migration targets** (the wave material): adminService, airportService,
|
||||
backupService,
|
||||
journeyService, journeyShareService, notificationService, oauthService,
|
||||
journeyService, journeyShareService, oauthService,
|
||||
weatherService, wikiService.
|
||||
- **Cross-cutting Wave-2 targets:** permissions and auditLog are done (2026-07) — see the
|
||||
DI-native list above; only tripAccess remains (delete, don't migrate).
|
||||
@@ -376,11 +432,13 @@ How to read it:
|
||||
already treats it as one — consumed by nest/auth, nest/oidc, nest/trip-invite and the
|
||||
plugin host; a candidate to fold into the trip-invite or trips domain some day, but
|
||||
nothing blocks on it),
|
||||
inAppNotifications, inAppNotificationActions, notificationPreferencesService, notifications
|
||||
inAppNotificationActions, notificationPreferencesService, notifications
|
||||
(+ `notifications/` registry), `memories/` cluster, `airtrail/` cluster. Several of these are
|
||||
themselves candidates to fold *into* a domain service when its domain migrates — and two
|
||||
already have: exchangeRateService → budget (2026-07) and **placeEnrichment → place**
|
||||
(2026-07), the latter also retiring a bridge. The four still reached only from
|
||||
themselves candidates to fold *into* a domain service when its domain migrates — and three
|
||||
already have: exchangeRateService → budget (2026-07), **placeEnrichment → place**
|
||||
(2026-07, the latter also retiring a bridge), and **inAppNotifications → notifications**
|
||||
(2026-08 — the in-app store folded with the dispatcher; the rest of the notifications
|
||||
cluster stayed plain on purpose). The four still reached only from
|
||||
`nest/places/*` (kmlImport, placeImage, placePhotoCache, unsplashService) are the
|
||||
next obvious fold candidates, but none of them blocks anything.
|
||||
|
||||
@@ -394,37 +452,48 @@ flowchart TD
|
||||
classDef infra fill:#455a64,color:#fff
|
||||
|
||||
subgraph frontier["READY FRONTIER (no unmigrated domain deps)"]
|
||||
admin[adminService]:::ready
|
||||
journey[journeyService]:::ready
|
||||
weather[weatherService]:::ready
|
||||
airport["airportService (boot special case)"]:::ready
|
||||
wiki[wikiService]:::ready
|
||||
oauth[oauthService]:::ready
|
||||
end
|
||||
|
||||
notifSvc[notificationService]:::blocked
|
||||
admin[adminService]:::blocked
|
||||
journey[journeyService]:::blocked
|
||||
journeyShare[journeyShareService]:::blocked
|
||||
backup["backupService (last by design)"]:::infra
|
||||
notifCluster["notifications cluster<br/>(notifications + prefs + inApp + channelRegistry)"]:::infra
|
||||
memories["memories/ cluster"]:::infra
|
||||
cleanup[userCleanupService]:::infra
|
||||
|
||||
notifSvc --> notifCluster
|
||||
admin --> notifSvc & cleanup
|
||||
admin --> cleanup
|
||||
journey --> memories
|
||||
journeyShare --> journey
|
||||
memories --> notifSvc & admin
|
||||
```
|
||||
|
||||
(The `authService` node is gone since the 2026-08-01 fold — the Wave-5 chain
|
||||
(The `notificationService` node — and the `notifications cluster` infra node
|
||||
under it — are gone since the 2026-08-02 fold: the dispatcher + in-app store
|
||||
went DI-native, the cluster's only remaining consumers are the DI-native
|
||||
`NotificationsService`'s plain imports (infra never blocks), and every legacy
|
||||
edge onto the domain became a `nest/notifications/notifications.bridge`
|
||||
repoint (adminService, memories/{unified,synology}, the scheduler's two lazy
|
||||
`require()`s) or an injection (AdminController, the plugin host's 24th
|
||||
factory dep). That cleared `adminService`'s last hard domain import — and,
|
||||
unpredicted, the `memories/` cluster's last domain edge with it, so **both
|
||||
adminService and journeyService moved into the frontier** (journey's only
|
||||
legacy deps are the avatarUrl helper + the now-pure-infra
|
||||
memories/photoResolver seam; journeyShareService stays blocked on
|
||||
journeyService itself). The lazy sends inside migrated Nest services keep
|
||||
their call-time shape, pointed at the bridge.
|
||||
The `authService` node is gone since the 2026-08-01 fold — the Wave-5 chain
|
||||
opener landed the same day its atlas blocker cleared. The three `--> auth`
|
||||
edges (admin/oidc/passkey, all `resolveAuthToggles`-class imports, passkey
|
||||
also `generateToken`/`stripUserForClient`/`avatarUrl`) became import-path-only
|
||||
repoints onto the 8-export `nest/auth/auth.bridge` (todo-bridge precedent),
|
||||
which put **oidcService and passkeyService on the frontier** — their only
|
||||
other legacy deps are helpers (apiKeyCrypto/tripMembership,
|
||||
webauthnConfig) — while `adminService` stays blocked on its hard
|
||||
`notificationService` import alone. The `oidcService` node is gone since the
|
||||
webauthnConfig) — while `adminService` stayed blocked on its hard
|
||||
`notificationService` import alone (cleared by the 2026-08-02 notifications
|
||||
fold above). The `oidcService` node is gone since the
|
||||
2026-08-01 fold later the same day — its `auth.bridge` repoint became the
|
||||
injected `AuthService` inside the DI-native `OidcService`, its
|
||||
apiKeyCrypto/tripMembership edges stay plain helper imports (helpers never
|
||||
@@ -454,11 +523,15 @@ the one `transit.mcp.ts` import. The `placeService` node is gone since the
|
||||
2026-07-30 fold — its legacy imports were
|
||||
all helpers, and the former `placeService → mapsService` edge (via `placeEnrichment`)
|
||||
died with the helper rather than becoming a repoint.
|
||||
`notificationService → notifications cluster` is a hard import — the former
|
||||
The former `notificationService → notifications cluster` hard import went
|
||||
DI-native with the 2026-08-02 fold (the cluster stays plain infra under
|
||||
`NotificationsService`); the even earlier
|
||||
`mapsService/transitService/webauthnConfig/
|
||||
oauthService → notifications` edges were only `getAppUrl`/`getMcpSafeUrl` and died with the
|
||||
Phase 0 move to `src/app-config`, which had put mapsService on the frontier. `memories/` ↔ admin/notificationService edges make the
|
||||
journey/memories corner tangle with the admin corner. The former
|
||||
Phase 0 move to `src/app-config`, which had put mapsService on the frontier.
|
||||
The `memories/ → admin` edge is what still tangles the journey/memories
|
||||
corner with the admin corner (its notificationService edge became a bridge
|
||||
repoint with the fold). The former
|
||||
`auth/collections/backup → permissions` and `notifSvc/oauth → auditLog` edges are gone since the
|
||||
2026-07 Wave-2 pair: the permissions consumers repointed to `nest/permissions/permissions.bridge`,
|
||||
the writeAudit consumers to `nest/audit/audit.bridge`, and the log*-only consumers to the plain
|
||||
@@ -472,26 +545,24 @@ never block).)
|
||||
|
||||
| service | imports (services/) | imported by (services/) | nest consumers | out-of-container consumers |
|
||||
|---|---|---|---|---|
|
||||
| `adminService` | apiKeyCrypto, avatarUrl, llmConfig, memories/helpersService, notificationService, passwordPolicy, userCleanupService (+ `permissions.bridge`, `addons.bridge`, `auth.bridge`) | (none) | nest/admin/admin.service.ts, nest/packing/packing.mcp.ts (`deletePackingTemplate`, the `admin-2` residual) | scheduler.ts (lazy) |
|
||||
| `adminService` | apiKeyCrypto, avatarUrl, llmConfig, memories/helpersService, passwordPolicy, userCleanupService (+ `permissions.bridge`, `addons.bridge`, `auth.bridge`, `notifications.bridge`) | (none) | nest/admin/admin.service.ts, nest/packing/packing.mcp.ts (`deletePackingTemplate`, the `admin-2` residual) | scheduler.ts (lazy) |
|
||||
| `airportService` | (none) | (none) | nest/airports/airports.service.ts, nest/booking-import/kitinerary-mapper.ts | db/database.ts (lazy boot backfill), mcp/tools/mapsWeather.ts, mcp/tools/transports.ts |
|
||||
| `apiKeyCrypto` | (none) | adminService, airtrail/airtrailService, llmConfig, memories/helpersService, memories/immichService, memories/photoResolverService, memories/synologyService, memories/unifiedService, notifications, unsplashService | nest/auth/auth.helpers.ts, nest/auth/auth.service.ts, nest/maps/maps.service.ts, nest/oidc/oidc.service.ts, nest/plugins/plugin-oauth.service.ts, nest/plugins/plugin-runtime.service.ts, nest/plugins/plugins.service.ts, nest/settings/settings.service.ts | db/migrations.ts |
|
||||
| `avatarUrl` | (none) | adminService, inAppNotifications, journeyService | nest/auth/auth.bridge.ts, nest/auth/auth.service.ts, nest/auth/passkey.service.ts, nest/budget/budget.service.ts, nest/collab/collab.service.ts, nest/files/files.service.ts, nest/packing/packing.service.ts, nest/reservations/reservations.service.ts, nest/trips/trips.service.ts | (none) |
|
||||
| `avatarUrl` | (none) | adminService, journeyService | nest/auth/auth.bridge.ts, nest/auth/auth.service.ts, nest/auth/passkey.service.ts, nest/budget/budget.service.ts, nest/collab/collab.service.ts, nest/files/files.service.ts, nest/notifications/notifications.service.ts, nest/packing/packing.service.ts, nest/reservations/reservations.service.ts, nest/trips/trips.service.ts | (none) |
|
||||
| `backupService` | (none — `permissions.bridge`, plugin backup/paths infra only) | (none) | nest/backup/backup.controller.ts, nest/backup/backup.service.ts | scheduler.ts (lazy) |
|
||||
| `conflictResult` | (none) | (none) | nest/packing/packing.controller.ts, nest/packing/packing.service.ts, nest/places/places.controller.ts, nest/places/places.service.ts, nest/plugins/host/plugin-host-deps.factory.ts | (none) |
|
||||
| `cookie` | (none) | (none) | nest/auth/auth-public.controller.ts, nest/auth/auth.service.ts, nest/auth/passkey.controller.ts, nest/oidc/oidc.controller.ts, nest/oidc/oidc.service.ts | (none) |
|
||||
| `demo` | (none) | (none) | nest/auth/auth.controller.ts, nest/auth/auth.service.ts, nest/collections/collections.controller.ts, nest/files/files.controller.ts, nest/places/places.controller.ts, nest/plugins/host/plugin-host-deps.factory.ts, nest/trips/trips.controller.ts | middleware/auth.ts, middleware/mfaPolicy.ts |
|
||||
| `distanceService` | (none) | (none) | nest/auth/auth.service.ts, nest/transit/transit-itinerary.helpers.ts | (none) |
|
||||
| `ephemeralTokens` | (none) | (none) | nest/auth/auth.service.ts, nest/files/files.service.ts | index.ts, websocket.ts |
|
||||
| `inAppNotificationActions` | (none) | inAppNotifications | (none) | (none) |
|
||||
| `inAppNotifications` | avatarUrl, inAppNotificationActions, notificationPreferencesService | notificationService | nest/notifications/notifications.service.ts | mcp/resources.ts, mcp/tools/notifications.ts |
|
||||
| `inAppNotificationActions` | (none) | (none) | nest/notifications/notifications.service.ts | (none) |
|
||||
| `journeyService` | avatarUrl, memories/photoResolverService | journeyShareService | nest/assignments/assignments.service.ts, nest/journey/journey.service.ts, nest/places/places.mcp.ts, nest/places/places.service.ts, nest/plugins/host/plugin-host-deps.factory.ts, nest/plugins/journal-entry-rows.controller.ts | mcp/resources.ts, mcp/tools/journey.ts |
|
||||
| `journeyShareService` | journeyService | (none) | nest/journey/journey.service.ts | mcp/tools/journey.ts |
|
||||
| `kmlImport` | (none) | (none) | nest/places/places.helpers.ts, nest/places/places.service.ts | (none) |
|
||||
| `llmConfig` | apiKeyCrypto | adminService | nest/llm-parse/llm-client.factory.ts, nest/llm-parse/llm-config.resolver.ts | (none) |
|
||||
| `mfaCrypto` | (none) | (none) | nest/auth/auth.service.ts | (none) |
|
||||
| `notificationPreferencesService` | notifications, notifications/builtins, notifications/channelRegistry | inAppNotifications, notificationService, notifications, notifications/channelRegistry | nest/admin/admin.service.ts, nest/notifications/notifications.service.ts, nest/plugins/install/manifest.ts | (none) |
|
||||
| `notifications` | apiKeyCrypto, notificationPreferencesService (+ `audit-log.logger`) | notificationPreferencesService, notificationService, notifications/builtins | nest/auth/auth.service.ts, nest/notifications/notifications.service.ts | (none) |
|
||||
| `notificationService` | inAppNotifications, notificationPreferencesService, notifications, notifications/channelRegistry (+ `audit-log.logger`) | adminService, memories/synologyService, memories/unifiedService | nest/admin/admin.controller.ts, nest/collab/collab.service.ts, nest/collections/collections.service.ts (lazy), nest/packing/packing.service.ts, nest/plugins/host/plugin-host-deps.factory.ts, nest/reservations/reservations.service.ts, nest/trips/trips.service.ts, nest/vacay/vacay.service.ts | scheduler.ts (lazy) |
|
||||
| `notificationPreferencesService` | notifications, notifications/builtins, notifications/channelRegistry | notifications, notifications/channelRegistry | nest/admin/admin.service.ts, nest/notifications/notifications.service.ts, nest/plugins/install/manifest.ts | (none) |
|
||||
| `notifications` | apiKeyCrypto, notificationPreferencesService (+ `audit-log.logger`) | notificationPreferencesService, notifications/builtins | nest/auth/auth.service.ts, nest/notifications/notifications.service.ts | (none) |
|
||||
| `oauthService` | (none — `addons.bridge` + `audit.bridge` only) | (none) | nest/oauth/oauth-api.controller.ts, nest/oauth/oauth.service.ts | mcp/index.ts, mcp/oauthProvider.ts |
|
||||
| `passwordPolicy` | (none) | adminService | nest/auth/auth.service.ts | (none) |
|
||||
| `placeImage` | (none) | (none) | nest/collections/collections.controller.ts, nest/collections/collections.service.ts, nest/common/place-image-upload.ts, nest/places/places.controller.ts, nest/places/places.service.ts | (none) |
|
||||
@@ -510,14 +581,18 @@ never block).)
|
||||
|
||||
- **`notifications/`**: `channelRegistry` ⇄ `notificationPreferencesService` (cycle),
|
||||
`builtins → notifications + channelRegistry`, and `notificationPreferencesService →
|
||||
builtins` (registering the built-in channels closes a second loop). Migrates as one unit
|
||||
with `notifications.ts`/`notificationService`.
|
||||
builtins` (registering the built-in channels closes a second loop). **Stayed plain infra
|
||||
through the 2026-08 notifications fold** — the DI-native `NotificationsService` consumes
|
||||
the cluster via plain imports (the cycles never blocked the fold), and only
|
||||
`inAppNotifications` folded into the service.
|
||||
- **`memories/`**: `helpersService` (base) ← immich/synology/unified/photoResolver;
|
||||
`photoResolverService` is the seam `journeyService` consumes (it also pulls
|
||||
immich/synology/thumbnail/`trekPhotoCache` — the latter is swept by `scheduler.ts`);
|
||||
`synology/unified → notificationService` couples this
|
||||
cluster to the admin corner (`thumbnailService`'s adminService edge was only
|
||||
`isAddonEnabled` — now `addons.bridge`); `immichService` writes audits via `nest/audit/audit.bridge`.
|
||||
`synology/unified` send via `nest/notifications/notifications.bridge` since the 2026-08
|
||||
fold — that was the cluster's **last legacy-domain edge** (`thumbnailService`'s
|
||||
adminService edge was only `isAddonEnabled` — `addons.bridge` since Phase 0), so the
|
||||
cluster is pure infra now and no longer blocks the journey corner; `immichService`
|
||||
writes audits via `nest/audit/audit.bridge`.
|
||||
- **`airtrail/`**: `airtrailClient` (base) ← mapper ← service ← import/sync; `import`/`sync`
|
||||
consume (since the 2026-07 reservations fold) the
|
||||
`nest/reservations/reservations.bridge` instead of the deleted `reservationService`
|
||||
@@ -530,33 +605,25 @@ never block).)
|
||||
|
||||
**Ready frontier** (all legacy deps are helpers, `tripAccess`, or lazy sends):
|
||||
|
||||
With step 4 closed by the 2026-08 transit-itinerary relocation, the pick is no
|
||||
longer on this table: the dependency-honest order's next step is the
|
||||
**notifications cluster → `notificationService`** fan-in (step 3, deferred
|
||||
while step 4 ran) — it must precede its admin/memories dependents. The
|
||||
frontier candidates below can still go any time:
|
||||
With the notifications fan-in cashed in on 2026-08-02 (step 3, the last
|
||||
deferred step), the dependency-honest order's next step is **adminService** —
|
||||
now on the frontier with the heaviest surface left (14 Nest + 11 MCP
|
||||
consumers, the `admin-2` residual, the two `AdminController.*` notification
|
||||
allow-list entries). The frontier candidates:
|
||||
|
||||
| Candidate | Why now / why not | Bridge tax (legacy dependents + out-of-container) |
|
||||
|---|---|---|
|
||||
| **adminService** | Unblocked by the 2026-08-02 notifications fold — its last hard domain import (`send`) became a `notifications.bridge` repoint; remaining legacy deps are all helpers (+ the four bridges). The order's official next step | `scheduler.ts` (lazy require), `systemNotices/conditions.ts` |
|
||||
| **journeyService** | Unblocked the same day, unpredicted: the memories cluster's last domain edge (`synology/unified → notificationService`) became a bridge repoint, leaving journey's deps pure infra (avatarUrl + memories/photoResolver). Coherence still says memories/journey land after admin | `mcp/resources.ts`, `mcp/tools/journey.ts`, `journeyShareService` |
|
||||
| **oauthService** | Dependency-free since the Phase 0 addons extraction (adminService edge was only `isAddonEnabled` → `addons.bridge`) — but the coherence order keeps it after admin so the `mcp/oauthProvider.ts` merge (`mcp-2`) lands with it. Its former table-mate **passkeyService cashed in on 2026-08-02** — the predicted "none (a leaf)" bridge tax held exactly | `mcp/index.ts`, `mcp/oauthProvider.ts` |
|
||||
| **weatherService / wikiService / airportService** | Independent leaves; airport has the `db/database.ts` boot lazy-require special case | little / none |
|
||||
|
||||
**Blocked, and by what (shortest unblock path):**
|
||||
|
||||
- `notificationService` ← notifications cluster (its auditLog edge is now the plain
|
||||
`audit-log.logger` import — gone as a blocker since the 2026-07 Wave-2 pair).
|
||||
- `adminService` corner: `authService` is DONE (2026-08 — the fold repointed
|
||||
adminService's `resolveAuthToggles` import to `auth.bridge`), so adminService is
|
||||
blocked by its hard `notificationService` import alone (userCleanupService is a
|
||||
helper — never blocks). `oauthService` is dependency-free since the
|
||||
Phase 0 addons extraction (its adminService edge was only `isAddonEnabled`, now
|
||||
`addons.bridge`), but the coherence order stays auth → admin → oauth so the
|
||||
`mcp/oauthProvider.ts` merge (`mcp-2`) lands after admin (permissions done
|
||||
2026-07; atlas + auth + oidc + passkey done 2026-08).
|
||||
- `journeyService` ← `memories/` cluster (which itself touches admin + notificationService) →
|
||||
`journeyShareService` after. Note the place fold added two more in-container
|
||||
journeyService consumers (`places.service.ts`'s hooks and `places.mcp.ts`'s
|
||||
skeleton reconcile), so this migration's repoint list grew by two.
|
||||
- `journeyShareService` ← `journeyService` (the only remaining domain-on-domain
|
||||
block). Note the place fold added two more in-container journeyService
|
||||
consumers (`places.service.ts`'s hooks and `places.mcp.ts`'s skeleton
|
||||
reconcile), so that migration's repoint list grew by two.
|
||||
- `backupService`: unblocked since the permissions fold (its edge is a
|
||||
`permissions.bridge` repoint now) but stays last by design (owns the
|
||||
closeDb/reinitialize lifecycle and the plugin backup/paths infra).
|
||||
@@ -573,7 +640,16 @@ Wave-2 `permissions` + `auditLog` pair were the first frontier picks — all don
|
||||
2. `tripService` (done 2026-07 — the hub folded into `TripsService`; the trips/share MCP
|
||||
surfaces moved to the decorator registry, the plugin host injects it, and the
|
||||
todo/share/collab/vacay bridges died with their last consumers)
|
||||
3. notifications cluster → `notificationService`
|
||||
3. notifications cluster → `notificationService` (done 2026-08-02 — deferred
|
||||
while steps 4–5 ran, then landed as one fold: the `send()` dispatcher +
|
||||
`inAppNotifications` store into the wrapper `NotificationsService` over
|
||||
DatabaseService + RealtimeService, the cluster deliberately staying plain
|
||||
infra; the 1-export `notifications.bridge.ts` covers scheduler +
|
||||
adminService + memories + the six lazy Nest sends, AdminController and the
|
||||
plugin host (24th factory dep) inject, the 5-tool registrar + in-app
|
||||
resource moved to `notifications.mcp.ts`, and the DTO ratchet cleared all
|
||||
five `NotificationsController` allow-list entries — **adminService and,
|
||||
unpredicted, journeyService joined the frontier**)
|
||||
4. `mapsService` (done 2026-07 — the geo core folded into `MapsService`, geo MCP
|
||||
tools onto the decorator registry, BookingImportService injects it, and a
|
||||
3-export `maps.bridge` covered placeEnrichment + the places registrar)
|
||||
@@ -811,6 +887,72 @@ Wave-2 `permissions` + `auditLog` pair were the first frontier picks — all don
|
||||
"Quirks fixed after the passkey fold" below; the rest (cross-user challenge burn,
|
||||
counter-0 clone exemption, unthrottled register/verify) are deliberate and stay.
|
||||
|
||||
- (2026-08-02 regeneration, post notifications fold) The heaviest bridge-tax row on the
|
||||
table resolved to **one bridge file with one export**: the registrar was a port as
|
||||
always (5 tools + the in-app resource onto `notifications.mcp.ts`), the plugin host
|
||||
was an injection, AdminController was an injection, and everything else — scheduler's
|
||||
two `require()`s, legacy adminService/memories, and the six lazy Nest sends — shares
|
||||
the single `send` re-export. Four shapes worth naming: (a) this is the first fold to
|
||||
deliberately leave **in-container** consumers on a bridge — the six fire-and-forget
|
||||
`import().then(({ send }) => …)` sends keep their call-time shape (path-only
|
||||
repoints) so a notification can never block or cycle a domain module; the how-to-read
|
||||
note's "lazy edges never block" rule now has a post-fold continuation clause. (b) The
|
||||
fold consumed a whole infra cluster *without folding it* — prefs/transports/
|
||||
channelRegistry (cycles and all) stay plain modules under the DI service, validating
|
||||
the "helpers stay plain" classification at fan-in scale — while `inAppNotifications`
|
||||
crossed the line the other way (the third helper to fold into its domain). (c) The
|
||||
unblock cascade over-delivered: the graph predicted adminService; it did not predict
|
||||
that repointing memories/{unified,synology} would erase the **memories cluster's**
|
||||
last domain edge and put journeyService on the frontier in the same stroke. (d) Two
|
||||
test-infrastructure risks the graph had never priced: the ~8 suites that warm the
|
||||
lazy import in `beforeAll` (`await import('…/notificationService')`) are hard-coupled
|
||||
to the module *path*, and the module e2e's DI conversion forced explicit
|
||||
`DatabaseModule`/`RealtimeModule` imports the moment the wrapper gained constructor
|
||||
deps — both now standing items for any fold with lazy consumers or a dep-free
|
||||
wrapper. The trailing `fix(server)` commit then cleared the verified defects the
|
||||
relocation had faithfully carried — see "Quirks fixed after the notifications fold"
|
||||
below.
|
||||
|
||||
## Quirks fixed after the notifications fold (the trailing `fix(server)` commit)
|
||||
|
||||
The relocation itself was byte-identical; these were then fixed on top, each with a
|
||||
regression pin, so the parity diff and the behaviour change stayed in separate commits:
|
||||
|
||||
1. **`toUtcIso` applied on only one of three `created_at` paths** — the #1149 fix
|
||||
normalized `createNotificationForRecipient`'s broadcast, but `createNotification`'s
|
||||
broadcast and `respond`'s post-update re-select/broadcast still emitted the raw
|
||||
SQLite `YYYY-MM-DD HH:MM:SS` string, which `new Date(...)` parses as LOCAL time on
|
||||
the client. All three paths now normalize (INOTIF-013, INOTIF-014).
|
||||
2. **`respond` ran the action handler BEFORE the response CAS** — a concurrent
|
||||
double-submit passed the `response IS NULL` pre-check twice and executed the action
|
||||
twice, with the loser still told `'Already responded'`. The CAS now claims the
|
||||
response first; a handler failure releases the claim (response back to NULL,
|
||||
`is_read` restored) so the legacy retry contract holds (INOTIF-011, INOTIF-012).
|
||||
3. **`synologyService`'s `synology_session_cleared` send was unawaited with no
|
||||
`.catch()`** — the only fire-and-forget send in the repo without one; an in-app
|
||||
insert failure became an unhandled rejection. It now carries the
|
||||
`.catch(() => {})` its `unifiedService` sibling always had.
|
||||
4. **Per-recipient dispatch-failure log interpolated the raw rejection** — logging
|
||||
`Error: gotify is down` where the admin-global path always unwrapped to the
|
||||
message; both paths unwrap now (NSVC-021).
|
||||
5. **MCP resource description drift** — `notifications-in-app` claimed "most recent
|
||||
50, unread first" but the query orders by `created_at DESC` only; the description
|
||||
dropped the unread-first claim (the query is untouched — changing the order would
|
||||
be a behaviour change, not a doc fix).
|
||||
|
||||
**Quirks deliberately preserved** (parity, not oversights): `actorId: 0` is coerced to
|
||||
"no actor" by the truthiness check (no real user has id 0); the `limit` defaulting
|
||||
divergence across layers (REST `parseInt(...) || 20` vs service `?? 20` vs MCP zod
|
||||
default — `limit=0` means 20 over REST and errors on MCP's `positive()`); the
|
||||
error-string divergence REST `'Not found'` (404) vs MCP `'Notification not found.'`
|
||||
(trailing period) vs service `'Notification not found'`, and respond failures always
|
||||
answering 400 (never 404, even for a missing notification); `resolveRecipients` user
|
||||
scope returning `[target]` for a nonexistent user (the FK on the insert rejects it
|
||||
downstream into the per-recipient `allSettled`/log path — noisy, not incorrect) and
|
||||
the trip owner joining recipients without a guest check (guests cannot own trips, so
|
||||
the members-only `COALESCE(is_guest,0)=0` filter is the reachable chokepoint); and
|
||||
the dev-only unknown-event notification carrying no `title_params`/`event_type`.
|
||||
|
||||
## Quirks fixed after the passkey fold (the trailing `fix(server)` commit)
|
||||
|
||||
The relocation itself was byte-identical; these two were then fixed on top, each with a
|
||||
|
||||
Reference in New Issue
Block a user