---
name: catalyst-project-structure
description: >
  Canonical hierarchy + decision trees for Aurora Catalyst backend (NestJS + DDD). Read this BEFORE
  creating any new file under backend/src/@(api|app|aurora|bridges)/. Covers where each layer lives,
  naming conventions, the @aurora-catalyst-generated + .origin reconciliation pattern (generated
  files ARE editable), and the shadow-module antipattern. Also fires reactively when the user asks
  "where does X go" or "what folder for Y". Loaded automatically by the architecture-checkpoint hook
  when a new architectural file is about to be written.
license: MIT
metadata:
  author: aurora
  version: '3.0'
  auto_invoke: >
    Creating a new file under backend/src/@(api|app|aurora|bridges)/; planning a new module,
    controller, handler, service, port, adapter, or bridge; user asks "where does X go"; 
    architecture-checkpoint hook surfaces a STOP message.
---

## ⚠️ Read this BEFORE writing any new architectural file

The `architecture-checkpoint` PreToolUse hook blocks the first `Write` to any new file in
`backend/src/@(api|app|aurora|bridges)/...` until this skill has been consulted. The skill is your
decision tree for where the code goes, what it gets named, and whether to extend an existing module
or create a new one.

If the hook surfaced the STOP banner — you're in the right place. Use the decision trees below to
verify the target path matches the canonical layout, then retry the Write.

---

## 🔑 The `@aurora-catalyst-generated` header is NOT a fence

Files with `@aurora-catalyst-generated` at the top are STARTING POINTS for customization, NOT
read-only zones. Cliter has a three-way merge system that handles user modifications on regen:

```
On `catalyst generate <bc>/<module>`:

  ┌─────────────────────────────────┐
  │ Current file (your edits)       │
  ├─────────────────────────────────┤
  │ .origin snapshot                │  ← what Cliter last emitted
  ├─────────────────────────────────┤
  │ New template output             │  ← what Cliter would emit now
  └─────────────────────────────────┘
                │
                ▼
   ┌────────────────────────────┐
   │ Three-way merge by Cliter  │
   └────────────────────────────┘
                │
        ┌───────┴───────┐
        ▼               ▼
   No conflict      Conflict
   ─────────       ────────
   Edit kept,     .origin.ts written
   merge clean    for manual reconcile
```

**Therefore**:

| Symptom you want to fix                                                      | Antipattern (do NOT do this)                                                                   | Correct action                                                                                                                                        |
| ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| "I need to add a controller to OAuthModule but it's generated"               | Create a parallel `@api/oauth/` shadow module to avoid touching `@api/o-auth/o-auth.module.ts` | Edit `o-auth.module.ts` directly. Add an inline `// manually maintained, re-apply after regen` comment near your edit. Cliter's `.origin` reconciles. |
| "The barrel `@app/o-auth/credential/index.ts` doesn't export my new service" | Re-export from a sibling barrel you create                                                     | Add your service to the existing barrel. It will survive most regens; conflicts surface as `.origin`.                                                 |
| "I need a new operation on an aggregate"                                     | Hand-write controller + handler + service from scratch in a custom dir                         | Declare it as `additionalApis` in the aggregate's `*.aurora.yaml` and regen. Cliter scaffolds the files; you fill the business logic.                 |

See also: `.claude/skills/catalyst-cli/origin-conciliation.md` for the reconciliation workflow, and
memory `[CLI scaffold vs codegen]` for the `.ts` (scaffold) vs `.eta` (regenerable) distinction.

---

## Layer responsibilities

| Layer              | Path                                     | Responsibility                                 | Editable?                                          |
| ------------------ | ---------------------------------------- | ---------------------------------------------- | -------------------------------------------------- |
| **API**            | `src/@api/[bc]/[module]/`                | Entry points (REST/GraphQL), DTOs, Resolvers   | YES (incl. generated files via `.origin`)          |
| **Application**    | `src/@app/[bc]/[module]/application/`    | Handlers and services per operation            | YES (incl. generated files via `.origin`)          |
| **Domain**         | `src/@app/[bc]/[module]/domain/`         | Field schemas, value objects, types            | YES (generated; hand-written sub-files allowed)    |
| **Infrastructure** | `src/@app/[bc]/[module]/infrastructure/` | Sequelize models, repositories, mocks          | YES (incl. generated files)                        |
| **Shared**         | `src/@aurora/`                           | Cross-BC infrastructure (decorators, modules)  | YES — this is hand-written, custom for the project |
| **Cross-BC**       | `src/@bridges/`                          | Ports + adapters bridging two bounded contexts | YES — hand-written, follows cross-BC skill         |

The `@aurora/` layer was historically described as "DO NOT MODIFY". That was WRONG. `@aurora/` is
hand-written shared infrastructure that the project owns and customizes (e.g.,
`@aurora/decorators/auth.decorator.ts`, `@aurora/modules/authentication/`, custom services). Edit
freely.

---

## Where the `.module.ts` lives (composition root convention)

NestJS `@Module()` declarations are **composition roots**, not domain code. They wire controllers
(`@api/`), handlers + services + repositories (`@app/`), and cross-BC adapters (`@bridges/`) into a
single DI container. Conceptually they belong to all three layers at once.

**Convention in Aurora Catalyst**: every BC's `.module.ts` lives in `@api/<bc>/<bc>.module.ts`.

| Module kind              | Path                                                      | Reason                                                |
| ------------------------ | --------------------------------------------------------- | ----------------------------------------------------- |
| BC composition root      | `@api/<bc>/<bc>.module.ts`                                | Cliter-generated; the BC's "front door" for NestJS DI |
| Cross-BC bridges         | `@bridges/bridges.module.ts`                              | Lives with its bridge providers                       |
| Authentication framework | `@aurora/modules/authentication/authentication.module.ts` | Lives with its feature                                |
| App root                 | `src/app.module.ts`                                       | NestJS bootstrap entrypoint                           |

Every module lives **where its feature lives**, except BC composition roots, which live in
`@api/<bc>/`. That's the asymmetry — and it's intentional.

### Why `@api/` and not `@app/` for BC composition roots

The dependency direction in Aurora Catalyst hexagonal layering is:

```
@api → @app → domain
   ↘     ↗
   @bridges
```

`@app` knows nothing about `@api`. Handlers, services, and repositories function without REST or
GraphQL — that's the proof-of-separation: you could swap the entire `@api/` layer and the
application layer would not notice.

If the `.module.ts` lived in `@app/<bc>/`, it would have to register controllers and resolvers from
`@api/`. That forces `@app` to import from `@api`, **inverting the dependency arrow**:

```
@app ↔ @api   ← circular dependency at the architecture level
```

NestJS resolves it at runtime (TypeScript doesn't care about layer rules), but conceptually it's a
ring, not a stack. You lose the property "the domain runs without presentation."

Keeping the module in `@api/` is **continuing the existing direction**: the outer layer (api)
already imports from the inner layer (app) via controllers. The module file participating in that
same direction is consistent, even if the module file itself is "more than just API."

### Why not a dedicated `@composition/` layer

Pure hexagonal would put composition roots in their own layer above api/app/bridges. The cost in
Aurora:

- New path alias to maintain in `tsconfig.json`
- Migrate every BC module + `bridges.module.ts` + `authentication.module.ts`
- Patch Cliter codegen templates
- Update every skill that references `@api/<bc>/<bc>.module.ts`

Benefit: architectural purism. Real-world impact on developers: zero (NestJS DI works the same,
imports are equally discoverable).

**Decision**: don't refactor. Document the convention so the next developer or AI knows it was a
deliberate trade-off, not an oversight.

### What `@api/` means in Aurora Catalyst

Read `@api/` as "**everything the outside world touches of this BC**":

- HTTP endpoints (controllers)
- GraphQL endpoints (resolvers)
- DTOs that cross the wire
- The `<bc>.module.ts` — the NestJS DI handle other modules import to consume the BC

Under that reading, the module file fits naturally. It's not "an HTTP endpoint" but it IS the public
contract for `imports: [IamModule]` consumers.

---

## Hand-written protocol-flow endpoints (non-aggregate)

Sometimes a BC needs hand-written REST/GraphQL endpoints that are NOT CRUD on a Cliter-managed
aggregate. Examples: OAuth2 token issuance (`POST /api/o-auth/token`), the JWKS endpoint
(`GET /.well-known/jwks.json`), webhook receivers, protocol flows. These are **first-class
features of the BC**, not Cliter-managed aggregates.

### The convention is: SAME shape as a Cliter aggregate, barrel + spread into `<bc>.module.ts`

Each Cliter aggregate has an `index.ts` barrel (e.g. `@api/o-auth/application/index.ts`) that
imports controllers + resolvers from their subdirs and exports them as ARRAYS
(`OAuthApplicationApiControllers`, `OAuthApplicationApiResolvers`) which `<bc>.module.ts` then
spreads into its `controllers:` / `providers:` arrays. Hand-written flows follow the SAME
pattern.

```
@api/<bc>/<flow>/
├── index.ts                    ← barrel — exports flow-scoped arrays
├── controllers/                ← REST controllers (one per endpoint)
│   ├── <bc>-<action>-<entity>.controller.ts
│   └── <bc>-<action>-<entity>.controller.spec.ts
├── resolvers/                  ← GraphQL resolvers (one per mutation/query)
│   ├── <bc>-<action>-<entity>.resolver.ts
│   └── <bc>-<action>-<entity>.resolver.spec.ts
└── graphql/                    ← GraphQL schema fragments
    └── <flow>.graphql
```

The familiarity matters: the next person reading the code sees the SAME directory structure
for aggregates and flows and doesn't have to learn two conventions.

### `<flow>/index.ts` shape (mirrors aggregate barrels)

```typescript
// @api/<bc>/<flow>/index.ts
export { <FlowFooController>, <FlowBarController> };
export { <FlowFooResolver> };
export { <FlowFooHandler>, <FlowBarHandler> };

import { <FlowFooController> } from './controllers/<foo>.controller';
import { <FlowBarController> } from './controllers/<bar>.controller';
import { <FlowFooResolver> } from './resolvers/<foo>.resolver';
import { <FlowFooHandler> } from '@app/<bc>/<feature>/application/<action>/<foo>.handler';
import { <FlowBarHandler> } from '@app/<bc>/<feature>/application/<action>/<bar>.handler';

export const <Bc>FlowsApiControllers = [<FlowFooController>, <FlowBarController>];
export const <Bc>FlowsApiResolvers = [<FlowFooResolver>];
export const <Bc>FlowsApiHandlers = [<FlowFooHandler>, <FlowBarHandler>];
```

### Registration in the Cliter-generated `<bc>.module.ts`

```typescript
// @aurora-catalyst-generated <bc>/<bc>.module.ts (header preserved)
import { <Bc>ApplicationApiControllers, <Bc>ApplicationApiResolvers } from './application';
// Manually maintained — hand-written flow barrel.
import { <Bc>FlowsApiControllers, <Bc>FlowsApiHandlers, <Bc>FlowsApiResolvers } from './flows';

@Module({
  controllers: [
    ...<Bc>ApplicationApiControllers,
    /* ...other Cliter aggregates */
    // Manually maintained — flow controllers.
    ...<Bc>FlowsApiControllers,
  ],
  providers: [
    ...<Bc>ApplicationApiResolvers,
    /* ...other Cliter aggregates */
    // Manually maintained — flow resolvers + composite/non-aggregate handlers.
    ...<Bc>FlowsApiResolvers,
    ...<Bc>FlowsApiHandlers,
    // Manually maintained — any sub-handlers that inject local services + repos.
    <SubHandlerA>,
    <SubHandlerB>,
  ],
})
export class <Bc>Module {}
```

The "manually maintained" sections of `<bc>.module.ts` shrink to: import the flow barrel,
spread its three arrays, plus any sub-handlers that need intra-module DI to the BC's
generated services. Cliter's three-way merge handles the regen.

### Why NOT a dedicated `<flow>.module.ts`

An earlier version of this convention recommended creating a dedicated `<flow>.module.ts`
imported by `app.module.ts` alongside the BC module. That was rejected because:

1. **Breaks the project's barrel-and-spread convention.** Every Cliter aggregate uses the
   `index.ts` pattern; hand-written flows that diverge force the reader to learn two
   conventions for the same kind of thing.
2. **Creates artificial module boundaries inside a single BC.** A "flow" is not its own
   bounded context — it's a feature of the existing BC. Splitting it into its own NestJS
   module fragments the BC's surface area without functional benefit.
3. **The supposed benefit (no manual code in `<bc>.module.ts`) was false.** `<bc>.module.ts`
   already has manually-maintained entries for sub-handlers, cross-BC adapters, retention
   crons, etc. Adding three spread lines does not change that reality.

### Worked example — OAuth2 flows in this repo

```
@api/o-auth/flows/
├── index.ts                                 ← exports OAuthFlowsApi{Controllers,Resolvers,Handlers}
├── controllers/
│   ├── o-auth-create-token.controller.ts    + .spec.ts
│   └── o-auth-get-jwks.controller.ts        + .spec.ts
├── resolvers/
│   └── o-auth-create-token.resolver.ts      + .spec.ts
└── graphql/
    └── flows.graphql

@app/o-auth/issue-token/application/
├── issue-token-response.types.ts
├── create/o-auth-create-token.handler.ts    (composite — dispatches PASSWORD vs REFRESH_TOKEN)
├── password/issue-token-password.handler.ts (sub-handler — injected by composite)
└── refresh/issue-token-refresh.handler.ts   (sub-handler — injected by composite)

@app/o-auth/jwks/application/
└── get/o-auth-get-jwks.handler.ts           (JWKS builder)
```

`@api/o-auth/o-auth.module.ts` (Cliter-generated, manually-maintained sections):
- Imports `OAuthFlowsApiControllers` + `OAuthFlowsApiResolvers` + `OAuthFlowsApiHandlers` from
  `./flows`.
- Spreads them in the module's `controllers:` / `providers:` arrays.
- Still registers `IssueTokenPasswordHandler` + `IssueTokenRefreshHandler` directly
  (sub-handlers — the composite injects them, and they need the local OAuth services).

### Antipattern — flat files in the directory root

```
@api/<bc>/<flow>/
├── action.controller.ts          ← FLAT — wrong
├── action.resolver.ts             ← FLAT — wrong
└── <flow>.graphql                ← FLAT — wrong
```

If you find yourself writing this layout, STOP and apply the canonical structure with
`controllers/` + `resolvers/` + `graphql/` subdirs. The flat layout looks "lighter" but it
fragments the convention: the next aggregate-like reader of this BC has to switch mental models
between "subdirs" (aggregates) and "flat" (flows). Pick one — and the project picked subdirs.

### Antipattern — registering hand-written code in the generated BC module

```typescript
// @api/<bc>/<bc>.module.ts (Cliter-generated)
@Module({
  controllers: [
    ...AggregateApiControllers,
    HandWrittenController,           // ← WRONG — pollutes the generated module
  ],
  providers: [
    ...AggregateApiResolvers,
    HandWrittenResolver,             // ← WRONG
    HandWrittenHandler,              // ← WRONG
  ],
})
```

This forces every Cliter regen of the BC to reconcile your hand-written entries via `.origin`,
even though semantically those entries are independent of the aggregate codegen. The dedicated
module pattern eliminates that friction.

### Why a single hand-written `.module.ts` IS justified here (and IS NOT a shadow module)

Earlier sections warn against creating "sibling top-level modules" (e.g.,
`@api/oauth/oauth.module.ts` next to `@api/o-auth/o-auth.module.ts`) — that IS the shadow-module
antipattern. The hand-written `<flow>.module.ts` is different:

- It lives INSIDE the BC's directory (`@api/<bc>/<flow>/<flow>.module.ts`), not parallel to it.
- It is IMPORTED by the BC's main module — there is a single composition root for the BC.
- Its scope is narrow and named: one flow, one module.

The shadow-module antipattern is about ESCAPING Cliter by creating a parallel BC; the dedicated
flow module is about ISOLATING hand-written code INSIDE an existing BC.

### Composite handlers — one business flow, two transports

When the same business flow is exposed over BOTH REST and GraphQL (e.g., OAuth token issuance via
`POST /api/o-auth/token` AND the GraphQL `oAuthCreateCredentials` mutation), all business logic —
including dispatch between variants (PASSWORD vs REFRESH_TOKEN vs ...) — MUST live in a single
handler in `@app/<bc>/<flow>/application/`. The controllers and resolvers parse the transport
shape and call that handler. They MUST NOT each implement their own dispatch.

See `catalyst-handler-composer` SKILL.md, "Composite handlers" section, for the dispatch pattern
and the antipattern detection.

### Worked example — OAuth2 flows in this repo

```
@api/o-auth/oauth-flows/
├── oauth-flows.module.ts                 ← dedicated module
├── controllers/
│   ├── issue-token.controller.ts         ← REST endpoint: POST /api/o-auth/token
│   └── jwks.controller.ts                ← REST endpoint: GET /.well-known/jwks.json
├── resolvers/
│   └── issue-token.resolver.ts           ← GraphQL mutation: oAuthCreateCredentials
└── graphql/
    └── oauth-flows.graphql               ← schema fragment for the mutation
```

```
@app/o-auth/issue-token/application/
├── issue-token.handler.ts                ← composite dispatch (PASSWORD | REFRESH_TOKEN)
├── issue-token-response.types.ts
├── password/issue-token-password.handler.ts
└── refresh/issue-token-refresh.handler.ts
```

Both `issue-token.controller.ts` (REST) and `issue-token.resolver.ts` (GraphQL) call
`IssueTokenHandler.main(input)`. The handler does the dispatch. The transports are slim parsers.

---

## Decision trees

### "I need a new file. Where does it go?"

```
Is the file a CRUD operation on an aggregate?
├─ YES → Declare it (or `additionalApis`) in cliter/<bc>/<module>.aurora.yaml
│         and regenerate. Cliter creates the scaffold in the right places.
└─ NO  → Continue ↓

Is the file a hand-written REST/GraphQL endpoint that is NOT a Cliter-generated aggregate
operation? (e.g., OAuth2 token endpoint, JWKS, custom webhooks, protocol flows)
├─ YES → SEE the dedicated section "Hand-written protocol-flow endpoints" below.
│         TL;DR: create a `<flow>/` directory under `@api/<bc>/` with the SAME shape Cliter
│         uses for aggregates (`controllers/`, `resolvers/`, `graphql/` subdirs) PLUS a
│         dedicated `<flow>.module.ts`. Do NOT put hand-written code flat in the directory,
│         and do NOT register it in the Cliter-generated `<bc>.module.ts`.
└─ NO  → Continue ↓

Is the file a NestJS provider/service that other BCs need to consume?
├─ YES → Declare a port in the CONSUMER BC at `@bridges/<consumer>/<feature>/ports/`,
│         and an adapter in the SUPPLIER BC at `@bridges/<supplier>/<module>/adapters/`.
│         Follow skill `catalyst-cross-bounded-context-ports`. Register both in `BridgesModule`.
└─ NO  → Continue ↓

Is the file a shared utility / decorator / module used by multiple BCs?
├─ YES → Goes in `@aurora/`. Examples: `@aurora/decorators/`, `@aurora/modules/<feature>/`,
│         `@aurora/services/`. Hand-written.
└─ NO  → Continue ↓

Is the file infrastructure-only (cron, retention, background tasks)?
└─ Place it in the BC that OWNS the data, at `@app/<bc>/shared/services/`.
   Register it (provider only — do NOT export) in the BC's `<bc>.module.ts`.
   WHY: keeping it in `@aurora` would require an `@aurora → @app/<bc>` import, which is
   an upward inversion forbidden by HR-PORTS-001. The service is concrete BC code
   (it knows the BC's tables, env vars, aggregates) — so the whole file moves to
   the BC, and the repository imports become intra-BC.
   REAL EXAMPLE: `@app/o-auth/shared/services/token-retention.service.ts`
   is registered as a provider in `@api/o-auth/o-auth.module.ts`.
```

### "I need to add a controller. Which module registers it?"

```
Is the controller for an aggregate already covered by Cliter (access-token, client, etc.)?
└─ YES → Cliter handles registration via the auto-generated `<bc>.module.ts`. Just regen.

Is the controller a hand-written protocol-flow endpoint (e.g., /oauth/token, /.well-known/jwks.json)?
└─ It belongs in a DEDICATED `<flow>.module.ts` under `@api/<bc>/<flow>/`. The generated
   `<bc>.module.ts` then imports that module. SEE the "Hand-written protocol-flow endpoints"
   section below for the full shape.

DO NOT create a sibling top-level module file (e.g., `@api/oauth/oauth.module.ts` next to
`@api/o-auth/o-auth.module.ts`) to host hand-written controllers. That's the shadow-module
antipattern. The dedicated `<flow>.module.ts` lives INSIDE the BC, not parallel to it.

DO NOT mix hand-written controllers/resolvers into the Cliter-generated `<bc>.module.ts`'s
`controllers:` / `providers:` arrays under a "manually maintained" comment. That blurs the
Cliter regen surface and forces every regen to reconcile your edits via `.origin`. The dedicated
module isolates hand-written code so Cliter regens of the BC do not touch it.
```

### "Two modules look like they overlap. What do I do?"

```
Are both modules in the same bounded context conceptually?
└─ YES → Merge: pick one (the generated one if it exists), move the hand-written controllers
          and providers in, delete the redundant module file. Update `app.module.ts` imports.
          The `@aurora-catalyst-generated` header is NOT a barrier — edit.

Are the modules in different bounded contexts (cross-BC dependency)?
└─ Use ports + adapters via `@bridges/`. Skill: `catalyst-cross-bounded-context-ports`.
```

---

## Naming conventions

Controllers, resolvers, handlers, services follow `<bc>-<operation>-<module>(.s)`:

```
o-auth-create-credential.controller.ts           # singular for single-item ops
o-auth-find-credential-by-id.controller.ts
o-auth-get-credentials.controller.ts             # plural for collection ops
o-auth-paginate-credentials.controller.ts
```

Module files: `<bc>.module.ts` for the BC's main module (Cliter-generated). For hand-written
auxiliary modules in the same BC, use a descriptive suffix: `<bc>-<feature>.module.ts`.

Bounded context names use `kebab-case` matching the YAML directory: `o-auth`, `iam`, `library`. The
corresponding URL prefixes also use kebab-case: `/api/o-auth/...`, NOT `/api/oauth/...`.

---

## Installed bounded contexts (live list)

The canonical source is `cliter/` at the monorepo root. As of this skill's last update:

| Path              | Bounded Context | Notes                                                                                |
| ----------------- | --------------- | ------------------------------------------------------------------------------------ |
| `cliter/iam/`     | `iam`           | Identity + Access Management (accounts, users, roles, tenants, permissions)          |
| `cliter/o-auth/`  | `o-auth`        | OAuth2 (clients, applications, access/refresh/credential tokens, authorization-code) |
| `cliter/library/` | `library`       | Sample library entities                                                              |

If you add a new BC to `cliter/`, run `catalyst generate back module ...` and update this table.

---

## Reference

- **YAML schemas**: `<project-root>/cliter/<bc>/<module>.aurora.yaml`
- **Cliter CLI workflow**: `.claude/skills/catalyst-cli/SKILL.md`
- **Origin reconciliation**: `.claude/skills/catalyst-cli/origin-conciliation.md`
- **Cross-BC ports**: `.claude/skills/catalyst-cross-bounded-context-ports/SKILL.md`
- **Handler composition**: `.claude/skills/catalyst-handler-composer/SKILL.md`
- **Hook source**: `.claude/hooks/architecture-checkpoint.ts`
