#Before changing code
- Read
.agents/README.mdand the binding for the application you will touch. - If a provider contract is involved, read
contracts/<provider>/anddocs/en/fidelity/<provider>.mdbefore the implementation. - Find the existing owning module and its nearest analogous test.
- Check
git status --shortand preserve unrelated local changes.
The repository is modular but deliberately not DDD. Do not introduce aggregates, domain events, repository interfaces or an application/domain/infrastructure split to implement an ordinary feature.
#Choose the right home
| Change | Put it here |
|---|---|
| Provider route, request validation or wire response | apps/api/src/providers/<id>/ |
| Rule shared by every provider | apps/api/src/core/ or the owning shared module |
| Payment state or multi-step write | apps/api/src/modules/payments/ |
| Accounting movement | apps/api/src/modules/ledger/ |
| Control-plane feature | apps/api/src/modules/<area>/ |
| Database model | apps/api/src/infrastructure/database/entities/ (current deferral) |
| Schema change | New file in apps/api/src/infrastructure/database/migrations/ |
| Console feature | apps/console/app/modules/<area>/ + thin page/Nitro route as needed |
| Shared console behaviour | apps/console/app/core/ |
| Checkout-neutral UI | apps/checkout/app/components/ |
| Provider checkout identity/flow | apps/checkout/providers/<id>/ |
| Built-in scenario | scenarios/<provider>/<name>.json |
#Add a backend endpoint
For a control-plane endpoint:
- Add or reuse a DTO under the owning module's
dto/when it writes data. DTOs useclass-validator; do not repeat the same validation by hand in the controller. - Add the route to the owning controller. Declare
RolesGuardandApplicationScopeGuardconsistently with sibling routes. - Put behaviour, transactions and persistence in the service.
- For a list, use
ListQueryDtoandpaginate()/paginateArray(). Declare an explicitsortable,filterableandsearchableallow-list. - Add an integration test. For row-scoped data, include a caller from another
tenant and expect
404. - If the console needs it, add the narrowest Nitro proxy route; never call the NestJS API from browser code.
The gateway wildcard controller is registered last in AppModule. New
control-plane modules must remain before GatewayModule, or the wildcard may
capture their routes.
#Change a provider operation
Start with the provider's official specification and fidelity document. Then:
- Update the provider route/contract/translation files only.
- Keep canonical operations within
CANONICAL_OPERATIONS. If the provider requires a genuinely new universal operation, treat that as an architecture change and update the contract and shared tests deliberately. - Do not inject services into the plugin. Translate to an
IntentCommandand letPaymentsServiceperform the write. - Update static and behavioural conformance cases plus official-SDK tests where the SDK can target the emulator.
- Update
docs/en/fidelity/<provider>.md, including limitations.
For a wholly new provider, follow Adding a provider.
#Add or change a scenario
Edit only the canonical JSON under scenarios/, then run:
npm run scenario:compile
This regenerates
apps/api/src/modules/scenarios/default-scenarios.ts. Build, typecheck and test
also compile scenarios first, but running the command immediately gives a clearer
validation error.
Use a canonical then.outcome; put provider vocabulary only in then.wire when
the exact provider status is the purpose of the test. Do not use arbitrary code.
See Scenarios for the supported DSL and current gaps.
#Add a database entity or field
-
Change or add the entity in
apps/api/src/infrastructure/database/entities/and export it fromentities/index.tswhen required by discovery. -
Add a new, ordered migration. Do not rewrite a migration that may already have run.
-
Apply it against a disposable PostgreSQL database:
npm run migrate -
Exercise the down:
npm run migrate -- --down, thennpm run migrateagain. A rollback nobody runs is a rollback that does not work. -
Add an integration test that relies on the new schema; compilation alone does not prove the migration and entity agree.
Money columns use integer minor units. Provider-varying payloads use JSONB only at the provider/resource or declared metadata boundary. Never add a mutable balance column.
#Add a console feature
The normal path is:
page/component -> module model/columns -> core gateway/composable
-> Nitro server route -> NestJS control-plane endpoint
Reuse createRestGateway, useEntityCollection and RTable rather than
assembling query strings or caching in a page. Keep credentials server-side in
Nitro. Run both npm run typecheck and npm run build: Nuxt auto-import and SSR
resolution problems are sometimes visible only in a real build.
#Add a checkout feature
Decide whether it is provider-neutral or provider identity:
- Summary, outcome and shell layout go in
apps/checkout/app/components/. - Provider-specific copy, layout and flow go in
apps/checkout/providers/<id>/. - API access goes through
apps/checkout/server/api/andserver/utils/api.ts.
Do not install or reuse the console's PrimeVue layer. Never accept a customer ID from the confirmation body to choose a wallet; the checkout session owns that identity.
#Add a test
- Pure transformation/invariant:
apps/api/test/*.spec.ts. - Database, HTTP, authorization or concurrency:
apps/api/test/integration/. - Provider-wide guarantee: parameterize the existing conformance suite.
- Provider SDK behaviour: SDK-specific spec, with telemetry/network disabled.
- Whole product acceptance: extend
scripts/stack-smoke.mjsonly for a critical cross-application path.
See Testing for commands and harness behaviour.
#Definition of done
Run from the repository root:
npm run typecheck
npm run lint
npm test
npm run build
npm run smoke
For a change involving migrations, sessions, Nitro routes, the worker or a cross-application flow, also run:
docker compose up --build --wait
npm run smoke:stack
Before handing off, verify documentation links and confirm that every path, command, environment variable and endpoint mentioned still exists.