Kora Framework 2.0 — Meta-skill
Single entry point for all Kora 2.0 development. This file is routing and rules only — the
implementation knowledge lives in the 39 sub-skills listed below.
|
|
| Framework |
Kora 2.0, group io.koraframework, BOM io.koraframework:kora-bom |
| Version |
2.0.0.RC1 — the only 2.0 release on Maven Central. 2.0.0-SNAPSHOT is the master development line and needs a snapshot repository |
| Java |
25 minimum. Kora 2.0 artifacts are compiled to class-file 69; older JVMs cannot load them |
| Gradle JVM |
The JDK running Gradle itself must also be ≥ 25 when io.koraframework:openapi-generator is on the buildscript classpath |
| Kotlin |
2.4.10 with KSP 2.3.11 — the versions the framework itself is built with |
| Build |
Gradle 9.5.1 (the wrapper the Kora 2.0 examples pin) |
This meta-skill is the single entry point for Kora Framework development. It routes to 39
specialized domain skills, each with its own narrow area of expertise.
Read this file first when:
- Starting a new Kora 2.0 microservice project from scratch (Java or Kotlin)
- Adding or refactoring a
@KoraApp application graph with *Module interfaces
- Choosing which Kora modules to plug in (HTTP, Database, Kafka, gRPC, SOAP, S3, Telemetry)
- Debugging DI container issues ("dependency not found", ambiguous bindings, graph build failures)
- Configuring typed config with
@ConfigSource / @ConfigMapper and environment substitution
- Planning a multi-module Gradle project with
@KoraSubmodule boundaries
- Migrating an existing Kora 1.x service — see §6
1. Operating rules
Four rules. They apply to every Kora task, on every turn, from the first message.
Each is stated once — here.
R0 is a gate: satisfy it before doing anything else. R1–R3 govern the work itself.
R0 — Ground the workspace before starting, on 2.0 refs
The upstream material is level 5 of the R1 chain and the final authority for every Kora question.
It must be on disk before you begin, not fetched reactively once you are already stuck.
Kora 2.0 has no published documentation site of its own. The kora-docs repository documents
Kora 1.x on every branch that currently exists, including feature/kora-2.0, whose docs/v2
directory is a byte-identical copy of the 1.x pages. Grounding on it would silently feed you
ru.tinkoff.kora APIs. So for 2.0 the ground truth is the framework source at a 2.0 ref plus
the migrated example applications, and nothing else.
Run this at the start of every Kora task. It is idempotent — it does nothing when the material
is already present, so there is no cost to running it every time:
if [ ! -d .kora-agent/kora-source-2.0 ] || [ ! -d .kora-agent/kora-examples-2.0 ]; then
mkdir -p .kora-agent
[ -d .kora-agent/kora-source-2.0 ] \
|| git clone --depth 1 --branch 2.0.0.RC1 \
https://github.com/kora-projects/kora.git .kora-agent/kora-source-2.0
[ -d .kora-agent/kora-examples-2.0 ] \
|| git clone --depth 1 --branch migration/2.0 \
https://github.com/kora-projects/kora-examples.git .kora-agent/kora-examples-2.0
rm -rf .kora-agent/kora-source-2.0/.git .kora-agent/kora-examples-2.0/.git
grep -qxF '.kora-agent/' .gitignore 2>/dev/null || echo '.kora-agent/' >> .gitignore
fi
Gate: .kora-agent/kora-source-2.0/ and .kora-agent/kora-examples-2.0/ both exist → proceed.
- Clone fails (no network, restricted environment) → say so explicitly and continue with sub-skills
only. Never silently substitute recollection for the source you could not fetch.
- The service targets
2.0.0-SNAPSHOT rather than 2.0.0.RC1 → clone the framework at master
instead, and say which ref you grounded on.
- The user declines the clone → note that level 5 is unavailable for this session, and flag any
answer that would normally have been verified against it.
Recovery: started Kora work and only then noticed .kora-agent/ is missing → run the block now,
then re-verify anything you already produced against it.
R1 — Route before you write
Resolve every Kora question through this chain, in order. Stop at the first level that answers it.
1. This file → pick the sub-skill
2. skills/<sub-skill>/SKILL.md → the actual expertise, templates, scripts
3. skills/<sub-skill>/references/ → detailed patterns for that domain
4. kora-journal (search) → known mistakes and fixes from past sessions
5. .kora-agent/kora-source-2.0/ → framework source: the final authority
.kora-agent/kora-examples-2.0/ → working migrated applications
- Never write Kora code straight from memory. Open the sub-skill first.
- Never skip to level 5 because "it's a small change". Levels 2–3 hold the vetted patterns.
- Sub-skill and source disagree → source wins; fix the sub-skill and journal it (R3).
- Within level 5, framework source outranks the examples: an example can be behind, the source cannot.
Recovery: caught writing Kora code without having opened the sub-skill → stop, discard the
draft, open the sub-skill, rewrite.
R2 — Kora only, and only what is asked
Kora is a self-contained framework with its own annotations, modules, and generated code.
- Never use Spring / Micronaut / Quarkus / Helidon annotations or idioms.
- Never invent a Kora annotation, class, or config key. If it is not in a sub-skill,
a
references/ file, or .kora-agent/, it does not exist — go verify it.
- Never carry a Kora 1.x API into 2.0 code.
ru.tinkoff.kora.* does not exist in 2.0, and
several 1.x names still compile while doing nothing (§4).
- Never add comments or Javadoc, unless the user asked for them or the logic is genuinely
opaque (bit manipulation, encodings, cryptography, non-obvious protocol handling).
- Never mix paradigms for one target: OpenAPI-generated controller → implement its delegate,
do not hand-write a parallel controller; Kora
@HttpClient → do not also call the same service
with a raw HTTP library.
- Always express behaviour through Kora's compile-time model — no reflection, no runtime proxies.
Recovery: a framework foreign to Kora slipped in → delete it, re-derive from the sub-skill,
journal it (R3).
R3 — Journal incorrect Kora usage
When you realise — or the user tells you — that you used the Kora Framework incorrectly,
record it. This is the feedback loop that improves the skills.
| Record |
Do not record |
| Wrong Kora annotation used |
Business / domain logic |
| Hallucinated Kora API or config key |
Project-specific workarounds |
| Kora 1.x API used in a 2.0 project |
Non-Kora issues |
| Kora pattern misapplied (DI, AOP, config, telemetry) |
UI/UX or style preferences |
| Kora best practice from a sub-skill violated |
Anything already correct |
| Sub-skill documentation wrong, stale, or unclear |
|
| Unrequested comments/Javadoc written (R2 breach) |
|
Entries are one file each, at ~/.kora-journal/<project>/<module>/<YYYY-MM-DD>_slug.md, shared
across all projects and sessions. Full CLI and workflow:
skills/kora-journal/SKILL.md.
Recovery: discovered a Kora mistake and moved on without an entry → add the entry now.
2. Per-task procedure
Follow these steps for every Kora request. Do not compress them.
- Satisfy R0 — run the grounding block, confirm
.kora-agent/ holds both 2.0 checkouts.
Do not begin step 1 until this gate passes or you have told the user it cannot.
- Classify the request against the routing tables in §3. More than one domain → handle them
one at a time, in dependency order (project setup → config → DI → domain modules → telemetry → tests).
- Read the sub-skill's
SKILL.md end to end, then the references/ entries it points at
for your case.
- Search the journal before implementing anything non-trivial:
# path is relative to this skill's own directory, not the project you are working in
python3 skills/kora-journal/scripts/kora_journal.py search "http interceptor auth" --limit 5
Hit → apply it, then mark it applied with integrate <entry-file>.
Miss → continue, and expect to add an entry afterwards under R3.
- Implement in the smallest increment that compiles — one annotation, method, or class at a time.
- Compile —
./gradlew clean classes. Mandatory after any annotation change; the annotation
processors, not the compiler, are what actually validate Kora code.
- Test — write
@KoraAppTest / Testcontainers coverage for real endpoints, queries, and
messages, then ./gradlew test.
- Verify the rules — R1 route followed, R2 no foreign framework, no 1.x API, no stray comments,
R3 journal entry added for any Kora mistake made along the way.
Definition of done: it compiles, tests pass, no rule was violated, journal updated if applicable.
Compilation is necessary but not sufficient in Kora 2.0. Ports, interceptor tags, telemetry
switches, circuit-breaker windows and mapper wiring all fail silently at runtime while the build
stays green (§4). Anything in that list needs a test, not a successful build.
Build commands
| Purpose |
Command |
| Compile + run annotation processors / KSP |
./gradlew clean classes |
| Run tests |
./gradlew test |
| First build after changing packages or generator settings |
./gradlew clean --continue then ./gradlew classes testClasses --continue --no-build-cache |
| Kotlin: see processor errors first |
./gradlew <module>:kspKotlin --console=plain |
Build hangs, or clean fails with "Unable to delete directory" |
./gradlew --stop, then retry |
3. Sub-skill routing
Read the matching sub-skill's SKILL.md before writing any code for that domain (R1).
Foundation — start here for new projects
| When the task is about |
Sub-skill |
| Gradle scaffolding, wrapper, build scripts, project layout (Java) |
kora-project-setup-java |
| Gradle scaffolding, KSP, Kotlin DSL (Kotlin) |
kora-project-setup-kotlin |
| Kora BOM, module artifacts, annotation processors, dependency choices |
kora-project-dependencies |
| Generating a runnable starter project (Initializr-style) |
generate_project.py |
HOCON config, typed @ConfigSource / @ConfigMapper, env substitution |
kora-config-hocon |
| YAML config (alternative to HOCON) |
kora-config-yaml |
Dependency injection
| When the task is about |
Sub-skill |
@KoraApp, @Component, @Module, factory methods, @KoraSubmodule, graph build failures |
kora-di-compile |
@Root, Lifecycle, @Tag, All<T>, ValueOf<T>, Wrapped<T>, @Conditional, GraphInterceptor |
kora-di-runtime |
Database
| When the task is about |
Sub-skill |
JDBC repositories, @EntityJdbc, @Query, SQL macros, transactions via executor().inTx(), Hikari |
kora-database-jdbc |
Cassandra / ScyllaDB, @EntityCassandra, @UDT, CQL, driver profiles |
kora-database-cassandra |
| Flyway / Liquibase migrations, SQL versioning |
kora-database-migration |
R2DBC and Vert.x SQL were removed in Kora 2.0. There is no reactive database integration —
JDBC on virtual threads is the only path.
Communication
| When the task is about |
Sub-skill |
HTTP server, @HttpController, @HttpRoute, @Path, @Query, interceptors |
kora-http-server |
HTTP server auth — Basic, Bearer, API keys, HttpServerPrincipalExtractor, principals |
kora-http-server-auth |
HTTP client, @HttpClient, declarative interfaces, interceptors, response mappers |
kora-http-client |
| HTTP client auth — Basic, Bearer, API keys, token refresh |
kora-http-client-auth |
gRPC server, GrpcServerModule, service handlers |
kora-grpc-server |
gRPC client, GrpcClientModule, generated stubs injected by type |
kora-grpc-client |
SOAP / WSDL client, SoapClientModule, generated clients |
kora-soap-client |
Kafka publishing, @KafkaPublisher, transactional producers |
kora-kafka-producer |
Kafka consuming, @KafkaListener, batch mode, error handling |
kora-kafka-consumer |
| OpenAPI → server code, delegates, controllers |
kora-openapi-generator-server |
OpenAPI → client code, typed Api interfaces |
kora-openapi-generator-client |
| Serving the spec — Swagger UI, Scalar, publishing |
kora-openapi-management |
JSON DTOs, @Json, sealed discriminators, custom (de)serialization |
kora-json |
Telemetry
| When the task is about |
Sub-skill |
| OpenTelemetry tracing, OTLP export, spans, Jaeger/Zipkin |
kora-telemetry-tracing |
| Micrometer metrics, Prometheus scrape endpoint, custom meters |
kora-telemetry-metrics |
| SLF4J / Logback, structured logs |
kora-telemetry-logging |
In Kora 2.0 component metrics and logging are disabled by default — see §4.
AOP
| When the task is about |
Sub-skill |
@Retryable, @CircuitBreakable, @Timeout, @RateLimited, @Fallback and their *Spec types |
kora-aop-resilient |
@Log, @Mdc, method logging aspects |
kora-aop-logging |
@Cacheable, @CachePut, @CacheInvalidate, @CacheInvalidateAll, Caffeine / Redis-Lettuce |
kora-aop-caching |
@ScheduleAtFixedRate, @ScheduleWithFixedDelay, @ScheduleOnce (JDK executor) |
kora-aop-scheduling-jdk |
Quartz scheduling, cron, @ScheduleWithTrigger, clustered jobs, job stores |
kora-aop-scheduling-quartz |
@Valid, @Validate, constraint annotations, custom validators |
kora-aop-validation |
Testing
| When the task is about |
Sub-skill |
@KoraAppTest, @TestComponent, mocks, JUnit 5 (Java) |
kora-testing-junit-java |
@KoraAppTest, MockK, JUnit 5 (Kotlin) |
kora-testing-junit-kotlin |
| Black-box E2E, Testcontainers, Docker |
kora-testing-blackbox |
Other
| When the task is about |
Sub-skill |
S3 object storage — declarative @S3 client and the AWS SDK wrapper |
kora-s3 |
| MapStruct mappers, DTO ↔ entity mapping |
kora-mapstruct |
| Recording incorrect Kora usage (R3), searching past mistakes |
kora-journal |
| Teaching Kora, guided tutorials, explaining concepts to a newcomer |
kora-teacher |
4. Architecture facts that drive decisions
Everything is generated at compile time. DI → *ComponentImpl / *Graph, HTTP →
*HttpRouter, AOP → *Aspect, JSON → *JsonReader / *JsonWriter, repositories →
*RepositoryImpl, OpenAPI → *Delegate. No reflection, no dynamic proxies, no runtime scanning.
The generated sources are the ground truth. When wiring or aspect behaviour is unclear,
read them:
- Java:
build/generated/sources/annotationProcessor/
- Kotlin:
build/generated/ksp/
Never edit generated code as a fix — regenerate instead.
Contracts are synchronous, executed on virtual threads. Reactive types (Mono/Flux) are
gone framework-wide — there is no response mapper for Mono/Flux — and suspend
repositories, controllers and HTTP clients are gone with them; the database KSP processor rejects
suspend outright with "Suspend methods are not supported by the repository generator."
Wrapping a Kora call in withContext(Dispatchers.IO) is now pointless overhead, and real
parallelism moves to Java StructuredTaskScope.
Async is not rejected everywhere, though, and what happens instead differs per generator —
never diagnose from the one-line rule.
| Generator |
Async shape |
What happens |
@KafkaPublisher |
Future/CompletionStage, + suspend/Deferred in Kotlin |
first-class and awaited — do not "fix" it |
@KafkaListener |
suspend, batch or key/value |
accepted, wrapped in runBlocking — compiles, buys nothing |
@KafkaListener |
suspend, single ConsumerRecord |
does not compile — no such branch |
@KafkaListener |
async return type |
silently discarded — see the silent-failure table |
| Cassandra repository |
CompletionStage |
accepted, Java only |
| repositories, controllers, HTTP clients, scheduling, SOAP |
any |
rejected with a named error |
Two things apply to every Kotlin async path above: runBlocking runs to completion on the calling
thread, so it buys no concurrency; and kotlinx-coroutines is a dependency of no Kora module,
so generated code that uses it compiles only if your project supplies coroutines itself. Write a
plain fun in listeners — because it is pointless, not because it is rejected.
Two verified exceptions, both source-backed:
Cassandra repositories still generate Java CompletionStage/CompletableFuture methods —
CassandraRepositoryGenerator emits prepareAsync(...).thenCompose(...), and the migrated
kora-java-database-cassandra example ships and tests one; the Kotlin side is synchronous there.
Kafka publishers accept async return types in both languages — the Java generator branches on
isFuture() || isCompletionStage(), the KSP one also on isSuspend() || isDeferred(), with a
passing testReturnRecordMetadataSuspend and a shipped Future<RecordMetadata> sendMetaAsync(...)
in kora-java-kafka. Do not "fix" either into a synchronous signature.
Context no longer exists anywhere in the framework. A signature that threaded a Kora
Context has to be rewritten, not mechanically patched.
Nullability is JSpecify in Java (org.jspecify.annotations.Nullable) and the type in
Kotlin (T?). JSpecify annotations are type-use: Outer.@Nullable Inner,
List<@Nullable String>, String @Nullable []. Kora contracts are @NullMarked, so Kotlin
overrides must match exactly — HttpServerResponseMapper<T>.apply(request, result: T?) with a
non-null result fails as 'apply' overrides nothing, a message that never mentions nullability.
Aspects need a non-final target. In Kotlin an AOP-annotated class and method must be open,
otherwise the aspect is silently not generated.
Silent failures — green build, broken service
These are the Kora 2.0 traps that compile cleanly and fail only at runtime. Treat every one as a
test case, never as something a successful build has proved.
| Symptom |
Cause |
| Kafka records vanish after a crash, and telemetry showed every one of them succeeding |
An async return type on a @KafkaListener. Neither consumer generator inspects the return type, so the call is emitted as a bare statement: handle() returns the instant the future is created, commitSync(offset + 1) runs immediately, and an exception completed inside the future reaches neither telemetry nor redelivery |
| Global interceptor never runs — auth/error handling silently gone |
@Tag(HttpServerModule.class). 2.0 collects interceptors by @Tag(HttpServer.class); the old class still exists, so it compiles |
| Service starts fine but nothing answers on the ports you configured |
publicApiHttpPort/privateApiHttpPort still in config. Unrecognised HOCON keys are ignored without a warning, so both servers fall back to their defaults — 8080 public, 8085 system. Probes, scrapers and load balancers hit nothing |
ConfigValueException: … got null at path: 'ROOT.jdbc.username' |
Datasource section still called db; 2.0 wires new JdbcDatabaseFactoryModule("jdbc") |
/metrics returns 200 but has no http_server_* / db_* series |
Component metrics default to off in 2.0. Set telemetry.metrics.enabled = true per component. Logging is off by default too; tracing is on |
| Tracing is on, spans are created, and the collector receives nothing |
The exporter's endpoint is unset. spanExporter/spanProcessor return SpanExporter.composite() / SpanProcessor.composite() — a no-op — with no warning, while tracing.enabled defaults to true, so the service looks fully instrumented |
No component found for dependency: …Mapper |
A mapper with constructor dependencies needs @Component — the generated module injects it rather than building it |
Multiple components match for a mapper |
A mapper without dependencies must not carry @Component — Kora constructs it itself. Decide per mapper, by its constructor |
Hundreds of package ru.tinkoff.kora … does not exist in files you never wrote |
Stale generator output in build/generated. ./gradlew clean --continue then classes testClasses --continue --no-build-cache |
ConfigValueException: … got null after parsing at path: 'ROOT.openapi.management.files' during graph build |
openapi.management.file — 2.0 reads files, a required List<String> with no default. It fails loudly at startup, even with enabled = false |
| Generated OpenAPI client hangs until timeout |
Config path must lower-case the first letter of the API name: PetApi → httpClient.<client>.petApi |
| Enum parsing fails only on real data |
Generated enums must be parsed with fromValue(raw), not valueOf |
| native-image builds fine, registrations never apply |
The file must be named reflect-config.json; reflection-config.json is ignored without a warning |
Troubleshooting
| Symptom |
Action |
Required dependency was not found: Foo |
Check @Component on the class, that the *Module is extended by @KoraApp, and that @KoraSubmodule exists in multi-module builds |
| Ambiguous dependency / more than one candidate |
Disambiguate with @Tag, or inject All<T> |
ApplicationGraph missing after clean |
Run ./gradlew classes — processors must run before anything references the graph |
| Aspect annotation has no effect |
Processor/KSP dependency missing, or the Kotlin class is not open |
incompatible types: String cannot be converted to Class<? extends Timeouter> |
1.x string-named resilient annotation; 2.0 takes a spec type |
KSP crashes with ClassCastException: String → KSType |
Same cause, seen from Kotlin: a leftover string-named resilient annotation |
error: SQL query placeholder has no matching method parameter: :id … - :arg0 |
Incremental build read the repository from a class file. --rerun-tasks or clean on the module |
| Generated classes stale or broken after a refactor |
Delete build/generated/, rebuild with --no-build-cache |
Build hangs, or clean fails to delete a directory |
./gradlew --stop, then retry |
| IDE shows errors but Gradle compiles fine |
IDE caching — invalidate caches and restart |
| Behaviour contradicts a sub-skill |
Verify against .kora-agent/kora-source-2.0/, fix the sub-skill, journal it (R3) |
5. Upstream sources
Availability of this material is R0, the gate in §1 — it is a precondition for starting work,
not a step you reach once you need it.
Module-by-module map of the framework source, the migrated example apps, and the areas this plugin
does not cover: references/kora-docs-map.md.
6. Coming from Kora 1.x
This plugin teaches and generates native Kora 2.0 code. It is not a migration tool.
- A project still on
ru.tinkoff.kora should use the kora-v1 plugin, which is maintained
separately and installs alongside this one.
- Migrating a real service is a semantic job, not a rename: typed resilient specifications, removed
Context, synchronous contracts replacing suspend/reactive chains, JSpecify placement, the S3
redesign and the generated-OpenAPI adaptation all require decisions a search-and-replace cannot
make. The upstream migration corpus lives at
kora-examples migration/2.0
and is fetched by the R0 block above as part of .kora-agent/kora-examples-2.0/.
- Sub-skills flag 1.x APIs where a 2.0 agent is likely to reach for one by habit. Those notes exist
to help you recognise and replace legacy input — never to justify emitting it.
1---2name: kora-v23description: Build and maintain Java/Kotlin services on the Kora Framework 2.0 (io.koraframework) — compile-time DI, zero reflection, synchronous contracts on virtual threads, annotation processors (Java) or KSP (Kotlin). Routes to 39 domain sub-skills. Use when the request mentions Kora, or uses Kora APIs: @KoraApp, @Component, @Module, @KoraSubmodule, @Root, @Tag, @Conditional, @FactoryModule, @HttpController, @HttpRoute, @HttpClient, @Repository, @Query, @EntityJdbc, @KafkaListener, @KafkaPublisher, gRPC, SOAP/WSDL, @S3.Client, @S3.Head, MapStruct, Konvert, @Json, @ConfigSource, @ConfigMapper (HOCON/YAML), OpenAPI codegen, @KoraAppTest, Testcontainers, @Valid, @Validate, @Log, @Mdc, @Retryable, @CircuitBreakable, @Timeout, @RateLimited, @Fallback, @Schedule*, @Cacheable, @CachePut, @CacheInvalidate, @CacheInvalidateAll, Micrometer/Prometheus metrics, OpenTelemetry/OTLP tracing, Undertow, Hikari. Also use for Kora project setup, Gradle/BOM dependencies, DI graph errors, or explaining Kora concepts. Do not use for Spring4license: Apache-2.05---67# Kora Framework 2.0 — Meta-skill89Single entry point for all Kora 2.0 development. This file is **routing and rules only** — the10implementation knowledge lives in the 39 sub-skills listed below.1112| | |13|---|---|14| **Framework** | Kora 2.0, group `io.koraframework`, BOM `io.koraframework:kora-bom` |15| **Version** | `2.0.0.RC1` — the only 2.0 release on Maven Central. `2.0.0-SNAPSHOT` is the `master` development line and needs a snapshot repository |16| **Java** | **25 minimum.** Kora 2.0 artifacts are compiled to class-file 69; older JVMs cannot load them |17| **Gradle JVM** | The JDK running Gradle itself must also be ≥ 25 when `io.koraframework:openapi-generator` is on the buildscript classpath |18| **Kotlin** | 2.4.10 with KSP 2.3.11 — the versions the framework itself is built with |19| **Build** | Gradle 9.5.1 (the wrapper the Kora 2.0 examples pin) |2021**This meta-skill is the single entry point for Kora Framework development.** It routes to 3922specialized domain skills, each with its own narrow area of expertise.2324**Read this file first when:**25- Starting a new Kora 2.0 microservice project from scratch (Java or Kotlin)26- Adding or refactoring a `@KoraApp` application graph with `*Module` interfaces27- Choosing which Kora modules to plug in (HTTP, Database, Kafka, gRPC, SOAP, S3, Telemetry)28- Debugging DI container issues ("dependency not found", ambiguous bindings, graph build failures)29- Configuring typed config with `@ConfigSource` / `@ConfigMapper` and environment substitution30- Planning a multi-module Gradle project with `@KoraSubmodule` boundaries31- Migrating an existing Kora 1.x service — see §63233---3435## 1. Operating rules3637Four rules. They apply to **every** Kora task, on every turn, from the first message.38Each is stated once — here.3940R0 is a **gate**: satisfy it before doing anything else. R1–R3 govern the work itself.4142### R0 — Ground the workspace before starting, on 2.0 refs4344The upstream material is level 5 of the R1 chain and the final authority for every Kora question.45It must be on disk **before** you begin, not fetched reactively once you are already stuck.4647**Kora 2.0 has no published documentation site of its own.** The `kora-docs` repository documents48Kora **1.x** on every branch that currently exists, including `feature/kora-2.0`, whose `docs/v2`49directory is a byte-identical copy of the 1.x pages. Grounding on it would silently feed you50`ru.tinkoff.kora` APIs. So for 2.0 the ground truth is the **framework source at a 2.0 ref** plus51the **migrated example applications**, and nothing else.5253**Run this at the start of every Kora task.** It is idempotent — it does nothing when the material54is already present, so there is no cost to running it every time:5556```bash57if [ ! -d .kora-agent/kora-source-2.0 ] || [ ! -d .kora-agent/kora-examples-2.0 ]; then58 mkdir -p .kora-agent59 [ -d .kora-agent/kora-source-2.0 ] \60 || git clone --depth 1 --branch 2.0.0.RC1 \61 https://github.com/kora-projects/kora.git .kora-agent/kora-source-2.062 [ -d .kora-agent/kora-examples-2.0 ] \63 || git clone --depth 1 --branch migration/2.0 \64 https://github.com/kora-projects/kora-examples.git .kora-agent/kora-examples-2.065 rm -rf .kora-agent/kora-source-2.0/.git .kora-agent/kora-examples-2.0/.git66 grep -qxF '.kora-agent/' .gitignore 2>/dev/null || echo '.kora-agent/' >> .gitignore67fi68```6970**Gate:** `.kora-agent/kora-source-2.0/` and `.kora-agent/kora-examples-2.0/` both exist → proceed.7172- Clone fails (no network, restricted environment) → say so explicitly and continue with sub-skills73 only. Never silently substitute recollection for the source you could not fetch.74- The service targets `2.0.0-SNAPSHOT` rather than `2.0.0.RC1` → clone the framework at `master`75 instead, and say which ref you grounded on.76- The user declines the clone → note that level 5 is unavailable for this session, and flag any77 answer that would normally have been verified against it.7879**Recovery:** started Kora work and only then noticed `.kora-agent/` is missing → run the block now,80then re-verify anything you already produced against it.8182### R1 — Route before you write8384Resolve every Kora question through this chain, in order. Stop at the first level that answers it.8586```871. This file → pick the sub-skill882. skills/<sub-skill>/SKILL.md → the actual expertise, templates, scripts893. skills/<sub-skill>/references/ → detailed patterns for that domain904. kora-journal (search) → known mistakes and fixes from past sessions915. .kora-agent/kora-source-2.0/ → framework source: the final authority92 .kora-agent/kora-examples-2.0/ → working migrated applications93```9495- **Never** write Kora code straight from memory. Open the sub-skill first.96- **Never** skip to level 5 because "it's a small change". Levels 2–3 hold the vetted patterns.97- Sub-skill and source disagree → **source wins**; fix the sub-skill and journal it (R3).98- Within level 5, framework source outranks the examples: an example can be behind, the source cannot.99100**Recovery:** caught writing Kora code without having opened the sub-skill → stop, discard the101draft, open the sub-skill, rewrite.102103### R2 — Kora only, and only what is asked104105Kora is a self-contained framework with its own annotations, modules, and generated code.106107- **Never** use Spring / Micronaut / Quarkus / Helidon annotations or idioms.108- **Never** invent a Kora annotation, class, or config key. If it is not in a sub-skill,109 a `references/` file, or `.kora-agent/`, it does not exist — go verify it.110- **Never** carry a Kora 1.x API into 2.0 code. `ru.tinkoff.kora.*` does not exist in 2.0, and111 several 1.x names still *compile* while doing nothing (§4).112- **Never** add comments or Javadoc, unless the user asked for them or the logic is genuinely113 opaque (bit manipulation, encodings, cryptography, non-obvious protocol handling).114- **Never** mix paradigms for one target: OpenAPI-generated controller → implement its delegate,115 do not hand-write a parallel controller; Kora `@HttpClient` → do not also call the same service116 with a raw HTTP library.117- **Always** express behaviour through Kora's compile-time model — no reflection, no runtime proxies.118119**Recovery:** a framework foreign to Kora slipped in → delete it, re-derive from the sub-skill,120journal it (R3).121122### R3 — Journal incorrect Kora usage123124When you realise — or the user tells you — that you used **the Kora Framework** incorrectly,125record it. This is the feedback loop that improves the skills.126127| Record | Do not record |128|---|---|129| Wrong Kora annotation used | Business / domain logic |130| Hallucinated Kora API or config key | Project-specific workarounds |131| Kora 1.x API used in a 2.0 project | Non-Kora issues |132| Kora pattern misapplied (DI, AOP, config, telemetry) | UI/UX or style preferences |133| Kora best practice from a sub-skill violated | Anything already correct |134| Sub-skill documentation wrong, stale, or unclear | |135| Unrequested comments/Javadoc written (R2 breach) | |136137Entries are one file each, at `~/.kora-journal/<project>/<module>/<YYYY-MM-DD>_slug.md`, shared138across all projects and sessions. Full CLI and workflow:139[`skills/kora-journal/SKILL.md`](skills/kora-journal/SKILL.md).140141**Recovery:** discovered a Kora mistake and moved on without an entry → add the entry now.142143---144145## 2. Per-task procedure146147Follow these steps for every Kora request. Do not compress them.1481490. **Satisfy R0** — run the grounding block, confirm `.kora-agent/` holds both 2.0 checkouts.150 Do not begin step 1 until this gate passes or you have told the user it cannot.1511. **Classify** the request against the routing tables in §3. More than one domain → handle them152 one at a time, in dependency order (project setup → config → DI → domain modules → telemetry → tests).1532. **Read** the sub-skill's `SKILL.md` end to end, then the `references/` entries it points at154 for your case.1553. **Search the journal** before implementing anything non-trivial:156 ```bash157 # path is relative to this skill's own directory, not the project you are working in158 python3 skills/kora-journal/scripts/kora_journal.py search "http interceptor auth" --limit 5159 ```160 Hit → apply it, then mark it applied with `integrate <entry-file>`.161 Miss → continue, and expect to add an entry afterwards under R3.1624. **Implement** in the smallest increment that compiles — one annotation, method, or class at a time.1635. **Compile** — `./gradlew clean classes`. Mandatory after any annotation change; the annotation164 processors, not the compiler, are what actually validate Kora code.1656. **Test** — write `@KoraAppTest` / Testcontainers coverage for real endpoints, queries, and166 messages, then `./gradlew test`.1677. **Verify the rules** — R1 route followed, R2 no foreign framework, no 1.x API, no stray comments,168 R3 journal entry added for any Kora mistake made along the way.169170**Definition of done:** it compiles, tests pass, no rule was violated, journal updated if applicable.171172> Compilation is necessary but **not sufficient** in Kora 2.0. Ports, interceptor tags, telemetry173> switches, circuit-breaker windows and mapper wiring all fail *silently* at runtime while the build174> stays green (§4). Anything in that list needs a test, not a successful build.175176### Build commands177178| Purpose | Command |179|---|---|180| Compile + run annotation processors / KSP | `./gradlew clean classes` |181| Run tests | `./gradlew test` |182| First build after changing packages or generator settings | `./gradlew clean --continue` then `./gradlew classes testClasses --continue --no-build-cache` |183| Kotlin: see processor errors first | `./gradlew <module>:kspKotlin --console=plain` |184| Build hangs, or `clean` fails with "Unable to delete directory" | `./gradlew --stop`, then retry |185186---187188## 3. Sub-skill routing189190Read the matching sub-skill's `SKILL.md` **before** writing any code for that domain (R1).191192### Foundation — start here for new projects193194| When the task is about | Sub-skill |195|---|---|196| Gradle scaffolding, wrapper, build scripts, project layout (Java) | [`kora-project-setup-java`](skills/kora-project-setup-java/SKILL.md) |197| Gradle scaffolding, KSP, Kotlin DSL (Kotlin) | [`kora-project-setup-kotlin`](skills/kora-project-setup-kotlin/SKILL.md) |198| Kora BOM, module artifacts, annotation processors, dependency choices | [`kora-project-dependencies`](skills/kora-project-dependencies/SKILL.md) |199| Generating a runnable starter project (Initializr-style) | [`generate_project.py`](skills/kora-project-dependencies/scripts/generate_project.py) |200| HOCON config, typed `@ConfigSource` / `@ConfigMapper`, env substitution | [`kora-config-hocon`](skills/kora-config-hocon/SKILL.md) |201| YAML config (alternative to HOCON) | [`kora-config-yaml`](skills/kora-config-yaml/SKILL.md) |202203### Dependency injection204205| When the task is about | Sub-skill |206|---|---|207| `@KoraApp`, `@Component`, `@Module`, factory methods, `@KoraSubmodule`, graph build failures | [`kora-di-compile`](skills/kora-di-compile/SKILL.md) |208| `@Root`, `Lifecycle`, `@Tag`, `All<T>`, `ValueOf<T>`, `Wrapped<T>`, `@Conditional`, `GraphInterceptor` | [`kora-di-runtime`](skills/kora-di-runtime/SKILL.md) |209210### Database211212| When the task is about | Sub-skill |213|---|---|214| JDBC repositories, `@EntityJdbc`, `@Query`, SQL macros, transactions via `executor().inTx()`, Hikari | [`kora-database-jdbc`](skills/kora-database-jdbc/SKILL.md) |215| Cassandra / ScyllaDB, `@EntityCassandra`, `@UDT`, CQL, driver profiles | [`kora-database-cassandra`](skills/kora-database-cassandra/SKILL.md) |216| Flyway / Liquibase migrations, SQL versioning | [`kora-database-migration`](skills/kora-database-migration/SKILL.md) |217218R2DBC and Vert.x SQL were **removed** in Kora 2.0. There is no reactive database integration —219JDBC on virtual threads is the only path.220221### Communication222223| When the task is about | Sub-skill |224|---|---|225| HTTP server, `@HttpController`, `@HttpRoute`, `@Path`, `@Query`, interceptors | [`kora-http-server`](skills/kora-http-server/SKILL.md) |226| HTTP server auth — Basic, Bearer, API keys, `HttpServerPrincipalExtractor`, principals | [`kora-http-server-auth`](skills/kora-http-server-auth/SKILL.md) |227| HTTP client, `@HttpClient`, declarative interfaces, interceptors, response mappers | [`kora-http-client`](skills/kora-http-client/SKILL.md) |228| HTTP client auth — Basic, Bearer, API keys, token refresh | [`kora-http-client-auth`](skills/kora-http-client-auth/SKILL.md) |229| gRPC server, `GrpcServerModule`, service handlers | [`kora-grpc-server`](skills/kora-grpc-server/SKILL.md) |230| gRPC client, `GrpcClientModule`, generated stubs injected by type | [`kora-grpc-client`](skills/kora-grpc-client/SKILL.md) |231| SOAP / WSDL client, `SoapClientModule`, generated clients | [`kora-soap-client`](skills/kora-soap-client/SKILL.md) |232| Kafka publishing, `@KafkaPublisher`, transactional producers | [`kora-kafka-producer`](skills/kora-kafka-producer/SKILL.md) |233| Kafka consuming, `@KafkaListener`, batch mode, error handling | [`kora-kafka-consumer`](skills/kora-kafka-consumer/SKILL.md) |234| OpenAPI → server code, delegates, controllers | [`kora-openapi-generator-server`](skills/kora-openapi-generator-server/SKILL.md) |235| OpenAPI → client code, typed `Api` interfaces | [`kora-openapi-generator-client`](skills/kora-openapi-generator-client/SKILL.md) |236| Serving the spec — Swagger UI, Scalar, publishing | [`kora-openapi-management`](skills/kora-openapi-management/SKILL.md) |237| JSON DTOs, `@Json`, sealed discriminators, custom (de)serialization | [`kora-json`](skills/kora-json/SKILL.md) |238239### Telemetry240241| When the task is about | Sub-skill |242|---|---|243| OpenTelemetry tracing, OTLP export, spans, Jaeger/Zipkin | [`kora-telemetry-tracing`](skills/kora-telemetry-tracing/SKILL.md) |244| Micrometer metrics, Prometheus scrape endpoint, custom meters | [`kora-telemetry-metrics`](skills/kora-telemetry-metrics/SKILL.md) |245| SLF4J / Logback, structured logs | [`kora-telemetry-logging`](skills/kora-telemetry-logging/SKILL.md) |246247In Kora 2.0 component **metrics and logging are disabled by default** — see §4.248249### AOP250251| When the task is about | Sub-skill |252|---|---|253| `@Retryable`, `@CircuitBreakable`, `@Timeout`, `@RateLimited`, `@Fallback` and their `*Spec` types | [`kora-aop-resilient`](skills/kora-aop-resilient/SKILL.md) |254| `@Log`, `@Mdc`, method logging aspects | [`kora-aop-logging`](skills/kora-aop-logging/SKILL.md) |255| `@Cacheable`, `@CachePut`, `@CacheInvalidate`, `@CacheInvalidateAll`, Caffeine / Redis-Lettuce | [`kora-aop-caching`](skills/kora-aop-caching/SKILL.md) |256| `@ScheduleAtFixedRate`, `@ScheduleWithFixedDelay`, `@ScheduleOnce` (JDK executor) | [`kora-aop-scheduling-jdk`](skills/kora-aop-scheduling-jdk/SKILL.md) |257| Quartz scheduling, cron, `@ScheduleWithTrigger`, clustered jobs, job stores | [`kora-aop-scheduling-quartz`](skills/kora-aop-scheduling-quartz/SKILL.md) |258| `@Valid`, `@Validate`, constraint annotations, custom validators | [`kora-aop-validation`](skills/kora-aop-validation/SKILL.md) |259260### Testing261262| When the task is about | Sub-skill |263|---|---|264| `@KoraAppTest`, `@TestComponent`, mocks, JUnit 5 (Java) | [`kora-testing-junit-java`](skills/kora-testing-junit-java/SKILL.md) |265| `@KoraAppTest`, MockK, JUnit 5 (Kotlin) | [`kora-testing-junit-kotlin`](skills/kora-testing-junit-kotlin/SKILL.md) |266| Black-box E2E, Testcontainers, Docker | [`kora-testing-blackbox`](skills/kora-testing-blackbox/SKILL.md) |267268### Other269270| When the task is about | Sub-skill |271|---|---|272| S3 object storage — declarative `@S3` client and the AWS SDK wrapper | [`kora-s3`](skills/kora-s3/SKILL.md) |273| MapStruct mappers, DTO ↔ entity mapping | [`kora-mapstruct`](skills/kora-mapstruct/SKILL.md) |274| Recording incorrect Kora usage (R3), searching past mistakes | [`kora-journal`](skills/kora-journal/SKILL.md) |275| Teaching Kora, guided tutorials, explaining concepts to a newcomer | [`kora-teacher`](skills/kora-teacher/SKILL.md) |276277---278279## 4. Architecture facts that drive decisions280281- **Everything is generated at compile time.** DI → `*ComponentImpl` / `*Graph`, HTTP →282 `*HttpRouter`, AOP → `*Aspect`, JSON → `*JsonReader` / `*JsonWriter`, repositories →283 `*RepositoryImpl`, OpenAPI → `*Delegate`. No reflection, no dynamic proxies, no runtime scanning.284- **The generated sources are the ground truth.** When wiring or aspect behaviour is unclear,285 read them:286 - Java: `build/generated/sources/annotationProcessor/`287 - Kotlin: `build/generated/ksp/`288 Never edit generated code as a fix — regenerate instead.289- **Contracts are synchronous, executed on virtual threads.** Reactive types (`Mono`/`Flux`) are290 gone framework-wide — there is no response mapper for `Mono`/`Flux` — and `suspend`291 repositories, controllers and HTTP clients are gone with them; the database KSP processor rejects292 `suspend` outright with *"Suspend methods are not supported by the repository generator."*293 Wrapping a Kora call in `withContext(Dispatchers.IO)` is now pointless overhead, and real294 parallelism moves to Java `StructuredTaskScope`.295296 **Async is not rejected everywhere, though, and what happens instead differs per generator —297 never diagnose from the one-line rule.**298299 | Generator | Async shape | What happens |300 |---|---|---|301 | `@KafkaPublisher` | `Future`/`CompletionStage`, + `suspend`/`Deferred` in Kotlin | **first-class and awaited** — do not "fix" it |302 | `@KafkaListener` | `suspend`, batch or key/value | accepted, wrapped in `runBlocking` — compiles, buys nothing |303 | `@KafkaListener` | `suspend`, single `ConsumerRecord` | **does not compile** — no such branch |304 | `@KafkaListener` | async **return type** | **silently discarded — see the silent-failure table** |305 | Cassandra repository | `CompletionStage` | accepted, **Java only** |306 | repositories, controllers, HTTP clients, scheduling, SOAP | any | rejected with a named error |307308 Two things apply to every Kotlin async path above: `runBlocking` runs to completion on the calling309 thread, so it buys no concurrency; and `kotlinx-coroutines` is a dependency of **no** Kora module,310 so generated code that uses it compiles only if your project supplies coroutines itself. Write a311 plain `fun` in listeners — because it is pointless, not because it is rejected.312 **Two verified exceptions, both source-backed:**313 *Cassandra repositories* still generate Java `CompletionStage`/`CompletableFuture` methods —314 `CassandraRepositoryGenerator` emits `prepareAsync(...).thenCompose(...)`, and the migrated315 `kora-java-database-cassandra` example ships and tests one; the Kotlin side is synchronous there.316 *Kafka publishers* accept async return types in **both** languages — the Java generator branches on317 `isFuture() || isCompletionStage()`, the KSP one also on `isSuspend() || isDeferred()`, with a318 passing `testReturnRecordMetadataSuspend` and a shipped `Future<RecordMetadata> sendMetaAsync(...)`319 in `kora-java-kafka`. Do not "fix" either into a synchronous signature.320- **`Context` no longer exists** anywhere in the framework. A signature that threaded a Kora321 `Context` has to be rewritten, not mechanically patched.322- **Nullability is JSpecify in Java** (`org.jspecify.annotations.Nullable`) and **the type in323 Kotlin** (`T?`). JSpecify annotations are *type-use*: `Outer.@Nullable Inner`,324 `List<@Nullable String>`, `String @Nullable []`. Kora contracts are `@NullMarked`, so Kotlin325 overrides must match exactly — `HttpServerResponseMapper<T>.apply(request, result: T?)` with a326 non-null `result` fails as `'apply' overrides nothing`, a message that never mentions nullability.327- **Aspects need a non-final target.** In Kotlin an AOP-annotated class and method must be `open`,328 otherwise the aspect is silently not generated.329330### Silent failures — green build, broken service331332These are the Kora 2.0 traps that compile cleanly and fail only at runtime. Treat every one as a333test case, never as something a successful build has proved.334335| Symptom | Cause |336|---|---|337| Kafka records vanish after a crash, and telemetry showed every one of them succeeding | An **async return type on a `@KafkaListener`**. Neither consumer generator inspects the return type, so the call is emitted as a bare statement: `handle()` returns the instant the future is created, `commitSync(offset + 1)` runs immediately, and an exception completed inside the future reaches neither telemetry nor redelivery |338| Global interceptor never runs — auth/error handling silently gone | `@Tag(HttpServerModule.class)`. 2.0 collects interceptors by `@Tag(HttpServer.class)`; the old class still exists, so it compiles |339| Service starts fine but nothing answers on the ports you configured | `publicApiHttpPort`/`privateApiHttpPort` still in config. Unrecognised HOCON keys are ignored without a warning, so both servers fall back to their defaults — 8080 public, 8085 system. Probes, scrapers and load balancers hit nothing |340| `ConfigValueException: … got null at path: 'ROOT.jdbc.username'` | Datasource section still called `db`; 2.0 wires `new JdbcDatabaseFactoryModule("jdbc")` |341| `/metrics` returns 200 but has no `http_server_*` / `db_*` series | Component metrics default to **off** in 2.0. Set `telemetry.metrics.enabled = true` per component. Logging is off by default too; tracing is on |342| Tracing is on, spans are created, and the collector receives nothing | The exporter's `endpoint` is unset. `spanExporter`/`spanProcessor` return `SpanExporter.composite()` / `SpanProcessor.composite()` — a no-op — with no warning, while `tracing.enabled` defaults to **true**, so the service looks fully instrumented |343| `No component found for dependency: …Mapper` | A mapper *with* constructor dependencies needs `@Component` — the generated module injects it rather than building it |344| `Multiple components match` for a mapper | A mapper *without* dependencies must **not** carry `@Component` — Kora constructs it itself. Decide per mapper, by its constructor |345| Hundreds of `package ru.tinkoff.kora … does not exist` in files you never wrote | Stale generator output in `build/generated`. `./gradlew clean --continue` then `classes testClasses --continue --no-build-cache` |346| `ConfigValueException: … got null after parsing at path: 'ROOT.openapi.management.files'` during graph build | `openapi.management.file` — 2.0 reads `files`, a **required** `List<String>` with no default. It fails loudly at startup, even with `enabled = false` |347| Generated OpenAPI client hangs until timeout | Config path must lower-case the first letter of the API name: `PetApi` → `httpClient.<client>.petApi` |348| Enum parsing fails only on real data | Generated enums must be parsed with `fromValue(raw)`, not `valueOf` |349| native-image builds fine, registrations never apply | The file must be named `reflect-config.json`; `reflection-config.json` is ignored without a warning |350351### Troubleshooting352353| Symptom | Action |354|---|---|355| `Required dependency was not found: Foo` | Check `@Component` on the class, that the `*Module` is extended by `@KoraApp`, and that `@KoraSubmodule` exists in multi-module builds |356| Ambiguous dependency / more than one candidate | Disambiguate with `@Tag`, or inject `All<T>` |357| `ApplicationGraph` missing after `clean` | Run `./gradlew classes` — processors must run before anything references the graph |358| Aspect annotation has no effect | Processor/KSP dependency missing, or the Kotlin class is not `open` |359| `incompatible types: String cannot be converted to Class<? extends Timeouter>` | 1.x string-named resilient annotation; 2.0 takes a spec **type** |360| KSP crashes with `ClassCastException: String → KSType` | Same cause, seen from Kotlin: a leftover string-named resilient annotation |361| `error: SQL query placeholder has no matching method parameter: :id … - :arg0` | Incremental build read the repository from a class file. `--rerun-tasks` or `clean` on the module |362| Generated classes stale or broken after a refactor | Delete `build/generated/`, rebuild with `--no-build-cache` |363| Build hangs, or `clean` fails to delete a directory | `./gradlew --stop`, then retry |364| IDE shows errors but Gradle compiles fine | IDE caching — invalidate caches and restart |365| Behaviour contradicts a sub-skill | Verify against `.kora-agent/kora-source-2.0/`, fix the sub-skill, journal it (R3) |366367---368369## 5. Upstream sources370371Availability of this material is **R0**, the gate in §1 — it is a precondition for starting work,372not a step you reach once you need it.373374Module-by-module map of the framework source, the migrated example apps, and the areas this plugin375does not cover: [`references/kora-docs-map.md`](references/kora-docs-map.md).376377---378379## 6. Coming from Kora 1.x380381This plugin teaches and generates **native Kora 2.0** code. It is not a migration tool.382383- A project still on `ru.tinkoff.kora` should use the **`kora-v1`** plugin, which is maintained384 separately and installs alongside this one.385- Migrating a real service is a semantic job, not a rename: typed resilient specifications, removed386 `Context`, synchronous contracts replacing `suspend`/reactive chains, JSpecify placement, the S3387 redesign and the generated-OpenAPI adaptation all require decisions a search-and-replace cannot388 make. The upstream migration corpus lives at389 [kora-examples `migration/2.0`](https://github.com/kora-projects/kora-examples/tree/migration/2.0/migration)390 and is fetched by the R0 block above as part of `.kora-agent/kora-examples-2.0/`.391- Sub-skills flag 1.x APIs where a 2.0 agent is likely to reach for one by habit. Those notes exist392 to help you *recognise and replace* legacy input — never to justify emitting it.