For the complete documentation index, see llms.txt. This page is also available as Markdown.

Anatomy of a Kerno test

What Kerno writes into your repository for each entry point, which file to read, and which one to leave alone.

Kerno writes its tests into your repository so they live with your code and travel through review like anything else. Each entry point gets its own folder.

The important thing to know first: every scenario exists as two files. The .scenario.md is written for you, and the .scenario.ts is written for the agent that runs it. When you want to know what a test does, read the Markdown.

What is in an entry point's folder

For POST /api/auth/login, Kerno creates .kerno/scenarios/endpoints/POST/api/auth/login/:

File
What it is
Read it?

<name>.scenario.md

The scenario in plain English. One per scenario

Yes

<name>.scenario.ts

The same scenario as executable TypeScript, which is what actually runs

Rarely

preconditions.ts

Checks that run before the entry point's scenarios, confirming the entry point is reachable and testable

Rarely

preconditions.md

The same checks in plain English

Rarely

plan.json

The plan the Markdown is rendered from

No

report.json

Results from the most recent run

No

<name>.scenario.blocked

Marks a scenario that could not run

No

Everything here is committed alongside your code.

The Markdown, which is the one to read

Each .scenario.md opens with an HTML comment marking it as generated, then follows the same section order every time. This is a real one, with the credential masked:

## Successful login with valid credentials returns tokens and user data
**Kind:** HAPPY_PATH | **Actor:** unauthenticated visitor | **Scope:** POST /api/auth/login

### Preconditions
- The database contains an active user with email `admin@example.com` and password hash matching `<password>`

### Trigger
POST /api/auth/login with JSON body `{ "email": "admin@example.com", "password": "<password>" }`

### Main success scenario
1. The endpoint validates the email format and password presence
2. The endpoint finds the active user by email
3. The endpoint verifies the plaintext password against the stored hash
4. The endpoint generates a JWT access token and a refresh token
5. The endpoint updates the user's `refreshToken` column and `lastLoginAt` timestamp
6. The endpoint responds 200 with JSON containing `user`, `token`, and `refresh_token`

### Postconditions
- Response status is `200`
- Response body `user.email` equals `"admin@example.com"`
- Response body `user.id` is a UUID string
- Response body `token` is a non-empty string
- The `token` value decodes as a valid JWT (does not throw on decode)
- A `user` table row with email `admin@example.com` exists with a non-null `refreshToken` column
- The same row's `lastLoginAt` column is set to a timestamp within the last 10 seconds

### Notes
The endpoint is throttled at 5 requests per 60 seconds per the `@Throttle` decorator. The
stored hash may be bcrypt or scrypt; the service auto-migrates to the current algorithm on
successful login.

Reading top to bottom you get what is worth knowing about any scenario. Sections marked optional appear only when the plan has them.

Section
What it tells you

Kind / Actor / Scope

What sort of case this is, who is making the request, and which entry point it hits

Preconditions

The state that has to exist before the request is sent

Trigger

The exact request

Main success scenario

What your code is expected to do, step by step

Postconditions

Every assertion, in plain English. This is the baseline

Minimal guarantees

Optional. What must hold even when the scenario fails

Cleanup

Optional. The teardown that runs after the assertions

Notes

Optional. What Kerno noticed and deliberately decided about, including what it chose to leave unasserted

The Postconditions section is the one to read closely. It is the baseline in words, so when a validation run reports a diff, the assertion that moved is written there.

The Notes section is worth a look when a scenario surprises you. Kerno records the judgement calls it made, such as an optional field it saw and chose to leave out of the assertions.

The TypeScript, which the agent maintains

The .scenario.ts is what the sandbox executes. It is a Vitest file that talks to your running application over HTTP and, in white box mode, to your datastores directly.

A scenario runs in up to four phases, and each one is a ctx call:

Phase
What it does

ctx.arrange

Sets up the state the Preconditions describe

ctx.act

Sends the request

ctx.assert

Checks the Postconditions

ctx.cleanUp

Removes whatever the scenario created

Two details explain why the generated code looks the way it does.

Assertions are soft. expect.soft lets a run finish and report every mismatch at once, so one validation tells you everything that moved instead of stopping at the first difference.

Every assertion carries a message. The second argument to each expect is the plain-English form of that postcondition, which is what you see in a run report when it fails.

Flow tests

A user flow gets one test that walks the journey in order. It lives under .kerno/scenarios/flows/<flow>/, beside a flow.json that records the flow's description, its steps and the outcome it should reach. In the TypeScript, each step of the journey runs inside its own ctx.step(...), and the test checks the outcome at the end. When a journey breaks, the result names the step where it failed.

Preconditions

preconditions.ts runs before the entry point's scenarios and checks, read-only, that testing the entry point is possible at all. What it checks depends on the entry point.

  • HTTP route. The application URL is set, the route is mounted and answering, and any credential the scenarios need can be obtained.

  • MCP tool. One read-only call to the tool answers.

  • Background consumer. Only the environment and seed data below, since a consumer is triggered through its broker and nothing signs in.

  • User flow. The service answers and the flow's actor can sign in.

Each also checks that the environment variables the run needs are set, and routes, tools and consumers check that their seed data exists. Reaching your databases and other dependencies is the environment healthcheck's job.

When a precondition fails, the run stops before any scenario runs and ends with an error naming the reason, which tells you nothing was tested. In an interactive run, Kerno first asks whether to retry with your guidance or abort. Validation runs reuse the tests on disk and skip preconditions.

Changing a test

Ask Kerno rather than editing the files.

Kerno re-plans, re-renders the Markdown, and rewrites the TypeScript together, so the two halves stay in step. An edit you make by hand is overwritten the next time that entry point is generated or updated.

Last updated