Files
agentic-dev/docs/superpowers/refactor-logs/2026-05-05-lazar-pattern-conformance.md
Danijel Martinek ee070c39b0 chore(refactor-log): complete Plan 8 changelog summary + Task 10 verification
All conformance checks pass per spec §12 acceptance criteria. Summary
section populated with 9 task commits, file count breakdown, and
verification results. Doc-update checklist (deferred) remains untouched
— ready for follow-up doc pass.

Test count: 244 → 325 (+81). All features now structurally conformant
with Lazar Nikolov Clean Architecture pattern (with intentional
divergences documented in spec §4).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 00:30:43 +02:00

32 KiB
Raw Blame History

Refactor Changelog — Lazar Pattern Conformance

Started: 2026-05-05 Spec: 2026-05-05-lazar-pattern-conformance-design.md Plan: 2026-05-05-plan-8-lazar-conformance.md Branch: feature/lazar-conformance

This document captures every architectural change made during Plan 8 execution, organized by category. After the plan is merged, use the "Doc update checklist" at the bottom to update external docs in a single follow-up pass.


Summary

Completed: 2026-05-05 Branch: feature/lazar-conformance Total commits: 9 (Tasks 1-10) Net test count change: 244 → 325 (+81 tests, +33% growth)

Category Count
Files added ~75 (factory functions, controllers, contracts, tests, real Payload impls, full media scaffold)
Files renamed (git mv) 27 (Task 3 file renames) + 17 (Task 2 entity moves) = 44
Files deleted 4 (articles.controller.ts, pages.controller.ts, 3 × entities/errors.ts)
Files modified ~80 (DI modules, tRPC routers, tests, factories, contracts, app boot, core-api)
Class renames 4 (PayloadXRepository → XRepository)
New DI symbols ~25 (use case + controller symbols across 5 features)

Tasks completed

Task Commit Description
1 16ca82d Refactor changelog scaffold
2 a4c4ca6 Entities split into models/ + errors/ subdirs
3 aa325f9 File renames (mock-.mock.ts, payload- dropped, -repository.repository) + class renames
4 780d5cb auth: factory-style + real UsersRepository + AuthenticationService
5 700d311 blog: factory-style + per-use-case controllers + new getArticleBySlug use case
6 353a41b marketing-pages: factory-style + per-use-case controllers
7 69623b9 navigation: factory-style + controller rename
8 8a36d80 media: full Clean Architecture scaffold (entities, application, infrastructure, interface-adapters, DI, integrations, contract, tests)
9-10 <this commit> Final verification + changelog completion

Conformance verification (Task 10)

All checks passed against the spec acceptance criteria (§12):

  • No entities/<x>.ts files at root level (only entities/models/<x>.ts + entities/errors/<x>.ts)
  • No mock-*.ts files in feature packages (only core-testing/src/payload/mock-payload-module.ts and core-testing/src/react/mock-trpc.ts remain — these are public test utilities, intentionally named)
  • No payload-*.ts files anywhere
  • No *-repository.interface.ts files (all use .repository.interface.ts dot-separated)
  • No *-service.interface.ts files
  • Every use case has export type I*UseCase = ReturnType<typeof xUseCase> alias
  • Every controller has export type I*Controller = ReturnType<typeof xController> alias
  • Every feature follows the §5 template
  • All tests pass (pnpm test: 325/325)
  • pnpm typecheck, pnpm lint, pnpm turbo boundaries clean

1. File renames (before → after)

Task 7: Controller rename

  • packages/navigation/src/interface-adapters/controllers/header.controller.tsget-header.controller.ts (verb-noun convention; git mv — history preserved)

Task 3: File and class renames

File renames — 27 files (git mv — history preserved):

auth:

  • packages/auth/src/infrastructure/repositories/mock-users.repository.tsusers.repository.mock.ts
  • packages/auth/src/infrastructure/repositories/mock-users.repository.test.tsusers.repository.mock.test.ts
  • packages/auth/src/application/repositories/users-repository.interface.tsusers.repository.interface.ts
  • packages/auth/src/infrastructure/services/mock-authentication.service.tsauthentication.service.mock.ts
  • packages/auth/src/application/services/authentication-service.interface.tsauthentication.service.interface.ts

blog:

  • packages/blog/src/infrastructure/repositories/mock-articles.repository.tsarticles.repository.mock.ts
  • packages/blog/src/infrastructure/repositories/mock-articles.repository.test.tsarticles.repository.mock.test.ts
  • packages/blog/src/infrastructure/repositories/payload-articles.repository.tsarticles.repository.ts
  • packages/blog/src/infrastructure/repositories/payload-articles.repository.test.tsarticles.repository.test.ts
  • packages/blog/src/application/repositories/articles-repository.interface.tsarticles.repository.interface.ts

marketing-pages:

  • packages/marketing-pages/src/infrastructure/repositories/mock-pages.repository.tspages.repository.mock.ts
  • packages/marketing-pages/src/infrastructure/repositories/mock-pages.repository.test.tspages.repository.mock.test.ts
  • packages/marketing-pages/src/infrastructure/repositories/payload-pages.repository.tspages.repository.ts
  • packages/marketing-pages/src/infrastructure/repositories/payload-pages.repository.test.tspages.repository.test.ts
  • packages/marketing-pages/src/application/repositories/pages-repository.interface.tspages.repository.interface.ts
  • packages/marketing-pages/src/infrastructure/repositories/mock-site-settings.repository.tssite-settings.repository.mock.ts
  • packages/marketing-pages/src/infrastructure/repositories/mock-site-settings.repository.test.tssite-settings.repository.mock.test.ts
  • packages/marketing-pages/src/infrastructure/repositories/payload-site-settings.repository.tssite-settings.repository.ts
  • packages/marketing-pages/src/infrastructure/repositories/payload-site-settings.repository.test.tssite-settings.repository.test.ts
  • packages/marketing-pages/src/application/repositories/site-settings-repository.interface.tssite-settings.repository.interface.ts

navigation:

  • packages/navigation/src/infrastructure/repositories/mock-header.repository.tsheader.repository.mock.ts
  • packages/navigation/src/infrastructure/repositories/mock-header.repository.test.tsheader.repository.mock.test.ts
  • packages/navigation/src/infrastructure/repositories/payload-header.repository.tsheader.repository.ts
  • packages/navigation/src/infrastructure/repositories/payload-header.repository.test.tsheader.repository.test.ts
  • packages/navigation/src/application/repositories/header-repository.interface.tsheader.repository.interface.ts

Class renames (4 classes — Payload prefix dropped; Mock prefix unchanged):

  • PayloadArticlesRepositoryArticlesRepository (in articles.repository.ts)
  • PayloadPagesRepositoryPagesRepository (in pages.repository.ts)
  • PayloadSiteSettingsRepositorySiteSettingsRepository (in site-settings.repository.ts)
  • PayloadHeaderRepositoryHeaderRepository (in header.repository.ts)

Consumers updated (imports + class references): DI modules, bind-production files, contract suites, use case source files, use case tests, controller tests, router tests, feature tests — approximately 35 files total.

Task 2: Entities split

Entity model moves (git mv — history preserved):

  • packages/auth/src/entities/user.tspackages/auth/src/entities/models/user.ts
  • packages/auth/src/entities/user.test.tspackages/auth/src/entities/models/user.test.ts
  • packages/auth/src/entities/session.tspackages/auth/src/entities/models/session.ts
  • packages/auth/src/entities/session.test.tspackages/auth/src/entities/models/session.test.ts
  • packages/auth/src/entities/cookie.tspackages/auth/src/entities/models/cookie.ts
  • packages/auth/src/entities/errors.test.tspackages/auth/src/entities/errors/errors.test.ts
  • packages/blog/src/entities/article.tspackages/blog/src/entities/models/article.ts
  • packages/blog/src/entities/article.test.tspackages/blog/src/entities/models/article.test.ts
  • packages/blog/src/entities/errors.test.tspackages/blog/src/entities/errors/errors.test.ts
  • packages/marketing-pages/src/entities/page.tspackages/marketing-pages/src/entities/models/page.ts
  • packages/marketing-pages/src/entities/page.test.tspackages/marketing-pages/src/entities/models/page.test.ts
  • packages/marketing-pages/src/entities/site-settings.tspackages/marketing-pages/src/entities/models/site-settings.ts
  • packages/marketing-pages/src/entities/site-settings.test.tspackages/marketing-pages/src/entities/models/site-settings.test.ts
  • packages/marketing-pages/src/entities/errors.test.tspackages/marketing-pages/src/entities/errors/errors.test.ts
  • packages/navigation/src/entities/header.tspackages/navigation/src/entities/models/header.ts
  • packages/navigation/src/entities/header.test.tspackages/navigation/src/entities/models/header.test.ts

2. Files added (with purpose)

Task 8: Media — full Clean Architecture scaffold (~30 new files)

Entities:

  • packages/media/src/entities/models/media.ts — Zod schema + Media type (id, alt, url, filename, mimeType, filesize, width?, height?)
  • packages/media/src/entities/models/media.test.ts — 4 schema validation tests
  • packages/media/src/entities/errors/media.tsMediaNotFoundError
  • packages/media/src/entities/errors/media.test.ts — 3 error class tests
  • packages/media/src/entities/errors/common.tsInputParseError (media copy)

Application:

  • packages/media/src/application/repositories/media.repository.interface.tsIMediaRepository with getMedia, listMedia, deleteMedia
  • packages/media/src/application/use-cases/get-media.use-case.ts — factory function; throws MediaNotFoundError when missing
  • packages/media/src/application/use-cases/get-media.use-case.test.ts — 2 tests
  • packages/media/src/application/use-cases/list-media.use-case.ts — factory function; delegates to repo
  • packages/media/src/application/use-cases/list-media.use-case.test.ts — 3 tests
  • packages/media/src/application/use-cases/delete-media.use-case.ts — factory function; throws MediaNotFoundError if not found, then deletes
  • packages/media/src/application/use-cases/delete-media.use-case.test.ts — 2 tests

Infrastructure:

  • packages/media/src/infrastructure/repositories/media.repository.mock.tsMockMediaRepository (in-memory; has _store test-helper method)
  • packages/media/src/infrastructure/repositories/media.repository.mock.test.ts — runs contract suite against mock
  • packages/media/src/infrastructure/repositories/media.repository.tsMediaRepository (Payload-backed; uses getPayload().find/findByID/delete)
  • packages/media/src/infrastructure/repositories/media.repository.test.ts — contract suite + impl-specific mapping tests (9 tests total)

Interface-adapters:

  • packages/media/src/interface-adapters/controllers/get-media.controller.ts — factory + IGetMediaController alias; Zod input validation
  • packages/media/src/interface-adapters/controllers/get-media.controller.test.ts — 4 tests
  • packages/media/src/interface-adapters/controllers/list-media.controller.ts — factory + IListMediaController alias
  • packages/media/src/interface-adapters/controllers/list-media.controller.test.ts — 3 tests
  • packages/media/src/interface-adapters/controllers/delete-media.controller.ts — factory + IDeleteMediaController alias
  • packages/media/src/interface-adapters/controllers/delete-media.controller.test.ts — 3 tests

DI:

  • packages/media/src/di/symbols.tsMEDIA_SYMBOLS (7 keys: repository + 3 use cases + 3 controllers)
  • packages/media/src/di/module.tsMediaModule: .to(MockMediaRepository) default + .toDynamicValue() bindings for all use cases and controllers
  • packages/media/src/di/container.tsmediaContainer singleton
  • packages/media/src/di/container.test.ts — 7 tests (all symbols resolve; default binding is mock)
  • packages/media/src/di/bind-production.tsbindProductionMedia(config) swaps mock for real MediaRepository

Integrations:

  • packages/media/src/integrations/api/router.tsmediaRouter (getMedia, listMedia, deleteMedia); controllers resolved via mediaContainer.get
  • packages/media/src/integrations/api/index.ts — re-exports mediaRouter + MediaRouter type

Contract + factory:

  • packages/media/src/__contracts__/media-repository.contract.ts — 6 contract assertions (undefined for missing id, empty list, delete-and-check, limit=0, offset, empty string id)
  • packages/media/src/__factories__/media.factory.ts — ADAPTED: now imports Media from ../entities/models/media instead of defining an inline interface
  • packages/media/src/__factories__/index.ts — ADAPTED: re-exports mediaFactory + Media type from correct paths

Feature test:

  • packages/media/tests/media.feature.test.ts — 7 tests; full slice via direct injection (no container)

Files modified in other packages:

  • packages/media/package.json — added inversify, reflect-metadata, zod, @repo/core-shared, @trpc/server to deps; added ./api and ./di/bind-production to exports
  • packages/core-api/package.json — added @repo/media: workspace:* to dependencies
  • packages/core-api/src/root.ts — added media: mediaRouter to appRouter; imported from @repo/media/api
  • packages/core-api/src/router.test.ts — updated assertion to include media. prefix check
  • apps/web-next/src/server/bind-production.ts — added bindProductionMedia(resolvedConfig) call
  • tsconfig.base.json — added @repo/media/api and @repo/media/di/bind-production path aliases

Task 4: Real implementations + tests

  • packages/auth/src/infrastructure/repositories/users.repository.ts — real Payload-backed UsersRepository (implements IUsersRepository via getPayload)
  • packages/auth/src/infrastructure/repositories/users.repository.test.ts — contract suite backed by an in-memory Payload stub (mirrors articles.repository.test.ts pattern)
  • packages/auth/src/infrastructure/services/authentication.service.ts — real AuthenticationService using node:crypto for hashing/UUIDs; session methods deferred (see §7)
  • packages/auth/src/infrastructure/services/authentication.service.test.ts — tests for generateUserId, hashPassword/verifyPassword round-trip, and deferred-method error assertions

Task 5: Blog factory refactor — new files

  • packages/blog/src/application/use-cases/get-article-by-slug.use-case.ts — NEW use case factory; throws ArticleNotFoundError when slug is not found (previously the controller hit the repo directly, bypassing use-case error handling)
  • packages/blog/src/application/use-cases/get-article-by-slug.use-case.test.ts — 2 tests (slug found, slug missing → ArticleNotFoundError)
  • packages/blog/src/interface-adapters/controllers/get-articles.controller.ts — factory controller, replaces the getArticlesController function from articles.controller.ts
  • packages/blog/src/interface-adapters/controllers/get-articles.controller.test.ts — 3 tests (valid input, status filter, invalid shape → InputParseError)
  • packages/blog/src/interface-adapters/controllers/create-article.controller.ts — factory controller, replaces createArticleController from articles.controller.ts
  • packages/blog/src/interface-adapters/controllers/create-article.controller.test.ts — 3 tests (valid, missing title, missing authorId)
  • packages/blog/src/interface-adapters/controllers/get-article-by-slug.controller.ts — factory controller; now delegates to getArticleBySlugUseCase (which throws ArticleNotFoundError) instead of calling repo directly
  • packages/blog/src/interface-adapters/controllers/get-article-by-slug.controller.test.ts — 3 tests (found, not found → ArticleNotFoundError, empty slug → InputParseError)

Task 6: Marketing-pages factory refactor — new files

  • packages/marketing-pages/src/interface-adapters/controllers/get-page-by-slug.controller.ts — factory controller; replaces getPageBySlugController function from pages.controller.ts; exports IGetPageBySlugController type alias
  • packages/marketing-pages/src/interface-adapters/controllers/get-page-by-slug.controller.test.ts — 3 tests (slug found, slug not found → undefined, missing slug → InputParseError)
  • packages/marketing-pages/src/interface-adapters/controllers/get-site-settings.controller.ts — factory controller; replaces getSiteSettingsController function from pages.controller.ts; exports IGetSiteSettingsController type alias
  • packages/marketing-pages/src/interface-adapters/controllers/get-site-settings.controller.test.ts — 1 test (returns site settings)

Task 2: Entities split — new error files

  • packages/auth/src/entities/errors/auth.ts — AuthenticationError, UnauthenticatedError, UnauthorizedError (split from errors.ts)
  • packages/auth/src/entities/errors/common.ts — InputParseError (auth copy)
  • packages/blog/src/entities/errors/article.ts — ArticleNotFoundError (split from errors.ts)
  • packages/blog/src/entities/errors/common.ts — InputParseError (blog copy)
  • packages/marketing-pages/src/entities/errors/page.ts — PageNotFoundError (split from errors.ts)
  • packages/marketing-pages/src/entities/errors/common.ts — InputParseError (marketing-pages copy)
  • packages/navigation/src/entities/errors/header.ts — HeaderNotFoundError (new; navigation had no errors.ts)
  • packages/navigation/src/entities/errors/common.ts — InputParseError (navigation copy)

3. Files deleted (with reason)

Task 5: Blog factory refactor — deleted files

  • packages/blog/src/interface-adapters/controllers/articles.controller.ts — multi-method controller replaced by 3 single-responsibility factory files (get-articles.controller.ts, create-article.controller.ts, get-article-by-slug.controller.ts)
  • packages/blog/src/interface-adapters/controllers/articles.controller.test.ts — deleted with the controller; tests rewritten in the per-controller test files

Task 6: Marketing-pages factory refactor — deleted files

  • packages/marketing-pages/src/interface-adapters/controllers/pages.controller.ts — multi-method controller replaced by 2 single-responsibility factory files (get-page-by-slug.controller.ts, get-site-settings.controller.ts)
  • packages/marketing-pages/src/interface-adapters/controllers/pages.controller.test.ts — deleted with the controller; tests rewritten in the per-controller test files

Task 2: Entities split — old errors.ts files removed

  • packages/auth/src/entities/errors.ts — replaced by errors/auth.ts + errors/common.ts
  • packages/blog/src/entities/errors.ts — replaced by errors/article.ts + errors/common.ts
  • packages/marketing-pages/src/entities/errors.ts — replaced by errors/page.ts + errors/common.ts
  • (navigation had no errors.ts to delete)

4. Pattern changes (code-level)

4.1 Use cases — factory function pattern

Applied to all 3 auth use cases (sign-in, sign-up, sign-out) in Task 4, all 3 blog use cases (get-articles, create-article, get-article-by-slug NEW) in Task 5, both marketing-pages use cases (get-page-by-slug, get-site-settings) in Task 6, the single navigation use case (get-header) in Task 7, and all 3 media use cases (get-media, list-media, delete-media NEW) in Task 8:

  • Use cases are now factory functions: (deps) => async (input) => result
  • Each file exports export type I*UseCase = ReturnType<typeof *UseCase> for DI typing
  • Use cases NO LONGER call *Container.get() inside their bodies — all dependencies are passed as factory arguments
  • Tests construct mocks directly: const useCase = getArticlesUseCase(repo); await useCase({ status: "draft" });
  • NEW getArticleBySlugUseCase: previously the slug lookup bypassed the use case layer (controller called repo directly); now the use case owns the ArticleNotFoundError throw
  • marketing-pages: getPageBySlugUseCase(pagesRepo) => async ({ slug }) => Page | undefined and getSiteSettingsUseCase(siteSettingsRepo) => async () => SiteSettings
  • navigation: getHeaderUseCase(headerRepo) => async () => Header; IGetHeaderUseCase type alias exported

4.2 Controllers — one per use case

Applied to all 3 auth controllers (sign-in, sign-up, sign-out) in Task 4; blog controllers split in Task 5; marketing-pages controllers split in Task 6; 3 new media controllers (get-media, list-media, delete-media) created from scratch in Task 8:

  • Controllers were already split for auth (one file per use case) — Task 4 refactors them to factory functions
  • Blog: the multi-method articles.controller.ts is deleted and replaced by 3 single-responsibility files
  • Marketing-pages: the multi-method pages.controller.ts is deleted and replaced by 2 single-responsibility files (get-page-by-slug.controller.ts, get-site-settings.controller.ts)
  • Navigation: header.controller.ts renamed to get-header.controller.ts (verb-noun) and converted to factory function; exports IGetHeaderController type alias
  • Factory pattern: (useCase: I*UseCase) => async (input) => result
  • Each exports export type I*Controller = ReturnType<typeof *Controller>
  • Validation (Zod safeParse) stays inside the controller factory; throws InputParseError on failure

4.3 Entities split — models/ + errors/ subdirs

Pattern now in place across auth, blog, marketing-pages, navigation (media skipped — no entities yet):

  • Entity Zod schemas + types live at entities/models/<x>.ts
  • Domain errors live at entities/errors/<domain>.ts (e.g. errors/auth.ts, errors/article.ts)
  • Shared InputParseError lives at entities/errors/common.ts (one copy per feature — ~6 lines each)
  • Colocated entity test files moved alongside their respective sources (models/*.test.ts, errors/errors.test.ts)
  • All imports across factories, contracts, repositories, use cases, controllers, tests, and src/index.ts updated to new paths
  • navigation divergence: no errors.ts existed pre-refactor; errors/header.ts (HeaderNotFoundError) and errors/common.ts added as new forward-looking stubs

5. DI changes

5.1 Inversify .toDynamicValue bindings

Applied to packages/auth/src/di/module.ts (Task 4), packages/blog/src/di/module.ts (Task 5), packages/marketing-pages/src/di/module.ts (Task 6), and packages/media/src/di/module.ts (Task 8):

auth:

  • AUTH_SYMBOLS expanded with 6 new keys: ISignInUseCase, ISignUpUseCase, ISignOutUseCase, ISignInController, ISignUpController, ISignOutController
  • Use cases bound with .toDynamicValue((ctx) => factoryFn(ctx.container.get(...))) — dependencies resolved from the container at call time
  • Controllers bound identically, taking the corresponding use case symbol from the container
  • Repository and service bindings remain .to(Mock*) as the default

blog:

  • BLOG_SYMBOLS expanded with 6 new keys: IGetArticlesUseCase, ICreateArticleUseCase, IGetArticleBySlugUseCase, IGetArticlesController, ICreateArticleController, IGetArticleBySlugController
  • All use cases and controllers bound with .toDynamicValue() — same pattern as auth
  • Repository binding remains .to(MockArticlesRepository) as the default
  • tRPC router (integrations/api/router.ts) updated to resolve controllers via blogContainer.get<IXController>(BLOG_SYMBOLS.IXController) instead of importing controllers directly

marketing-pages:

  • MARKETING_PAGES_SYMBOLS expanded with 4 new keys: IGetPageBySlugUseCase, IGetSiteSettingsUseCase, IGetPageBySlugController, IGetSiteSettingsController
  • Both use cases and both controllers bound with .toDynamicValue() — each use case receives its own repository from the container; each controller receives its use case
  • Repository bindings remain .to(MockPagesRepository) and .to(MockSiteSettingsRepository) as defaults
  • tRPC router updated to resolve controllers via marketingPagesContainer.get<IXController>(MARKETING_PAGES_SYMBOLS.IXController)

navigation:

  • NAVIGATION_SYMBOLS expanded with 2 new keys: IGetHeaderUseCase, IGetHeaderController
  • Use case and controller bound with .toDynamicValue((ctx) => factoryFn(ctx.container.get(...))) — same pattern as other features
  • Repository binding remains .to(MockHeaderRepository) as default
  • tRPC router updated to resolve controller via navigationContainer.get<IGetHeaderController>(NAVIGATION_SYMBOLS.IGetHeaderController)

media (Task 8 — new from scratch):

  • MEDIA_SYMBOLS defined with 7 keys: IMediaRepository, IGetMediaUseCase, IListMediaUseCase, IDeleteMediaUseCase, IGetMediaController, IListMediaController, IDeleteMediaController
  • Repository bound with .to(MockMediaRepository) as default
  • All 3 use cases + all 3 controllers bound with .toDynamicValue((ctx) => factoryFn(ctx.container.get(...)))
  • mediaRouter added to appRouter in packages/core-api/src/root.ts
  • bindProductionMedia(config) added to apps/web-next/src/server/bind-production.ts

5.2 Mock siblings registered as default bindings

  • MockUsersRepository and MockAuthenticationService remain the default bindings in AuthModule
  • bindProductionAuth(config: SanitizedConfig) now swaps both to UsersRepository and AuthenticationService (real Payload-backed implementations)
  • Previously bindProductionAuth was a no-op; it now rebinds both symbols using .toConstantValue(new RealImpl(config))

6. Test refactor patterns

6.1 Direct injection (no container rebinding)

Pattern also applied to all new media tests (Task 8) — use case and controller tests construct MockMediaRepository directly and inject into factory functions; the feature test (tests/media.feature.test.ts) builds the full chain via buildChain() helper without touching the DI container.

Applied to all auth use-case tests, controller tests, the router test, and the feature integration test:

  • Before: beforeEach unbinds and rebinds symbols on authContainer
  • After: each test (or it) constructs its own MockUsersRepository + MockAuthenticationService, then calls the factory directly:
    const users = new MockUsersRepository([]);
    const auth = new MockAuthenticationService(users);
    const useCase = signInUseCase(users, auth);
    const result = await useCase({ username: "alice", password: "testpassword" });
    
  • No authContainer.unbind() / authContainer.bind() calls remain in test files
  • The authRouter test and container.test.ts still reference authContainer (unavoidable — the router resolves controllers via the container, and the container test verifies the DI wiring), but they use the default bindings without rebinding
  • The feature test (tests/sign-in-flow.feature.test.ts) fully constructs the chain via direct injection rather than calling authRouter.createCaller({})

7. Open issues / deferred decisions

Task 4: AuthenticationService — deferred session methods

Three methods on AuthenticationService (in packages/auth/src/infrastructure/services/authentication.service.ts) are deferred because Payload's auth API does not map cleanly to the generic IAuthenticationService session interface:

Method Why deferred
createSession(user) Payload creates sessions via its REST /api/users/login endpoint and returns a JWT token. Mapping this to a generic { session: Session; cookie: Cookie } shape requires knowing Payload's JWT payload structure, the session expiry, and the exact cookie name/attributes Payload uses — which vary by collection config.
validateSession(sessionId) Payload validates sessions by verifying a JWT. This requires calling payload.auth() or payload.find() with the token, and is tightly coupled to Payload's internal token format.
invalidateSession(sessionId) Payload's default auth strategy is stateless JWT — there is no server-side session store to clear. Invalidation is done client-side by expiring the cookie. A proper implementation would require either a token blocklist or switching to Payload's API keys feature.

All three throw new NotImplementedError("methodName") with a clear message ("NotImplemented: AuthenticationService.<method> — see refactor log §7"). The mock (MockAuthenticationService) handles all test paths.

TODO: Revisit once the session cookie strategy is finalized. Consider:

  • Using Payload's local API payload.auth() for validateSession
  • Implementing a Redis-backed token blocklist for invalidateSession
  • Or replacing the IAuthenticationService interface with Payload-specific abstractions

Doc update checklist (deferred — run after merge)

After Plan 8 is merged to main, work through this list in a single batched doc-update pass. Each item points at an external doc that references the pre-Plan-8 layout or pattern and needs to be brought in line.

  • CLAUDE.md — Key Conventions section: update file path examples to use entities/models/<x>.ts; mention factory-function use cases; show I*UseCase type aliases
  • AGENTS.md (root) — Per-Package Conventions: update naming examples (<x>.repository.{ts,mock.ts,interface.ts}); add note about I*UseCase / I*Controller type aliases; document factory-style DI
  • docs/guides/adding-a-feature.md — restructure to use factory-function pattern in every step; update file paths to new layout; show .toDynamicValue() bindings
  • docs/guides/tdd-workflow.md — update "When to mock" decision tree to show direct factory injection (signInUseCase(mocks)(input)) instead of container rebinding; update factory usage examples to reference entities/models/* paths
  • docs/guides/testing-strategy.md — Mocking section: remove DI-rebinding pattern as the default; show direct factory injection
  • docs/architecture/vertical-feature-spec.md — update §10 (file shape examples) to new template; update §13 (testing) to reflect factory pattern + direct injection
  • docs/architecture/overview.md — layer descriptions: mention factory-function use cases; add note that controllers are one-per-use-case
  • docs/architecture/dependency-flow.md — verify dep flow still accurate with new DI bindings; update examples if any reference old paths
  • docs/decisions/adr-012-lazar-conformance.md — NEW ADR documenting the conformance decision and the four intentional divergences (per-feature DI, inversify retained, colocated tests, no Sentry/instrumentation services)
  • Per-feature AGENTS.md (auth/blog/media/marketing-pages/navigation) — update file path references; document factory pattern; update Tests section
  • packages/core-testing/AGENTS.md — update factory examples to reference entities/models/<x>.ts paths
  • packages/auth/AGENTS.md — document the new real UsersRepository and AuthenticationService (Payload-backed)
  • packages/media/AGENTS.md — full rewrite — media now has all Clean Architecture layers (entities, application, infrastructure, interface-adapters, DI, integrations/api)
  • Plan 7 plan/spec docs — add a one-line note at the top noting that paths reference the pre-Plan-8 layout; link to refactor changelog

Notes for the doc-update pass author

When updating external docs, apply these substitutions globally:

Old reference New reference
entities/<x>.ts entities/models/<x>.ts
entities/errors.ts entities/errors/<domain>.ts (or entities/errors/common.ts)
mock-<x>.repository.ts <x>.repository.mock.ts
payload-<x>.repository.ts <x>.repository.ts (real impl is now the canonical name)
<x>-repository.interface.ts <x>.repository.interface.ts
class PayloadXRepository class XRepository
Use case calls container.get(...) inside its body Use case is a factory function: (deps) => async (input) => result
Controller bundles multiple methods (articles.controller.ts) One controller per use case (get-articles.controller.ts, create-article.controller.ts)
Test does container.unbind(...).bind(...) in beforeEach Test constructs mocks and injects: const useCase = signInUseCase(mockUsers, mockAuth);
DI binds use case with .to(...) DI binds use case with .toDynamicValue((ctx) => factoryFn(ctx.container.get(...)))
Use case + controller imports inject via container Imports take dependencies as constructor/factory args

Add I*UseCase and I*Controller type alias examples wherever use cases / controllers are explained.