Migrating from 3.0 to 3.1
3.1 turns the generated backends into services you can keep: several features per service, an application layer wired to the runtime kernel, and generation that never overwrites implemented code. Nothing in gherkin-ai.config.json has to change. The generated output does change, so plan one regeneration per service.
Start with:
ghk generate -f features/<feature>.feature --dry-run # what would be created, updated or kept, per file
ghk upgrade # the full upgrade report
What changes in the generated code
| Area | 3.0 | 3.1 |
|---|---|---|
| HTTP layer | Placeholders (MediatR handlers, JPA outbox, NestJS CQRS module, in-memory stores) not connected to the kernel. | POST /api/v1/<feature>/{id}/{command} runs the runtime kernel's CommandService: idempotency key, outbox, traceparent. 202 created, 200 replayed, 409 in progress, 422 domain error. |
| Tenant | Taken from the X-Tenant-Id header, "default" when missing. |
Taken only from a validated HS256 bearer token (JWT_SECRET, claim security.tenantClaim, default tenant_id). No token or no tenant: 401. X-Tenant-Id is ignored. |
| Outbox publishing | Not wired. | A relay worker publishes the outbox (in-process for NestJS, Express, Spring, ASP.NET Core, FastAPI, Go, Phoenix and Axum; manage.py outbox_relay, artisan outbox:relay, rails outbox:relay otherwise). |
| Several features | Service-wide files (contracts, runtime, routes, Prisma schema) described only the last generated feature. | Each feature owns contracts/<feature>/; the root openapi.json / asyncapi.json, the Prisma schema, the runtime command service registry and the service routes cover every generated feature. |
| RabbitMQ topology | <feature>.events / .consumer / .dlx / .dlq. |
<service>.events / … (service = projectName), shared by every aggregate. |
| Regeneration | Overwrote every file. | Overwrites a file only if it is unchanged since it was generated; otherwise writes <file>.ghk-new. |
Steps
Set the token secret. Add
JWT_SECRETto the service environment (.env.examplelists it). If your identity provider puts the tenant in another claim, set it:{ "security": { "tenantClaim": "org_id" } }Clients that sent
X-Tenant-Idmust send a bearer token carrying the tenant instead.Protect what you implemented. Generation already keeps edited files (checksum in
.ghe/inventory.json). To keep files that are not in the inventory, or whole folders, list them:{ "generation": { "protect": ["src/domain/**", "src/**/*.service.ts"] } }A
.ghk-implementedfile at the project root (the LMS convention) protects every existing file that cannot be proven unedited (no matching checksum).--forceoverwrites anyway.If you edited the 3.0 runtime kernel (as the LMS did), regeneration keeps it and writes the new kernel as
.ghk-new; the new controllers call the 3.1 command service (withaggregateType), so merge the kernel.ghk-newfiles before building.generatewarns when this happens. A project with its own web layer can delete the generated controllers and keep calling its kernel.Regenerate every feature of the service, one
ghk generate -f <feature>per feature (order does not matter; each run includes the features already generated). Then:- merge each reported
<file>.ghk-newinto your file, or delete it; - delete the 3.0 placeholders you never edited.
ghk upgrade --apply --pruneremoves them when the inventory proves they are unedited. The removed generators are NestJScqrs,outbox,saga,idempotencyand theX-Tenant-Idmiddleware; JavaCQRS,Outbox,Saga,IdempotencyandMultiTenancy; the .NET MediatR handlers, MassTransit saga, EF outbox/inbox,HttpHeaderTenantServiceand Testcontainers mock; the Django ORM outbox; and the Go/Python pattern packages.
- merge each reported
Frontends:
- Entry. Every feature's panel is mounted (Vue
main.ts, Reactmain.tsx, AngularAppComponent, Next.jsapp/page.tsx, React NativeApp.tsx, Fluttermain.dart). - React tests that called
createAppStore(client)now passcreateAppStore({ <feature>: client }). - Angular: delete the old
src/app/api/client.token.ts; each feature now has<feature>-client.token.ts. - API clients now send
Authorization: Bearer <token>(passtoken) instead oftenantId.
- Entry. Every feature's panel is mounted (Vue
Rust only: the domain moved from
src/domain.rstosrc/domain/mod.rsplus one module per feature. Deletesrc/domain.rs(Rust refuses both).Naming (optional). JVM packages and the .NET namespace can be set explicitly:
{ "generation": { "package": "com.acme.billing", "groupId": "com.acme" } }Verify against real infrastructure:
ghk verify --services # PostgreSQL 17 + RabbitMQ 4, runs the stack's integration suite ghk converge --all # every feature of every project in the repository
Kernel updates from now on
ghk upgrade --kernel regenerates only the runtime kernel with the installed CLI: unedited kernel files are replaced, edited ones get .ghk-new. --all covers every project under the current directory; --dry-run only reports.
Lint
A new rule, GHK015, warns when a command or aggregate name contains digits, looks like example data, or has more than four words. Fix it with an @aggregate:<Name> tag or an explicit "<Name>Command" in the When step. Quoted example values ("MATH-101") are no longer reported by GHK006 / GHK007.