Skip to content

Commit 6f437f4

Browse files
Add CLAUDE.md and skill to translate stuff from shared core
1 parent 456a8ee commit 6f437f4

File tree

2 files changed

+279
-0
lines changed

2 files changed

+279
-0
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
---
2+
name: translate-from-shared-core
3+
description: Translate Rust changes from restatedev/sdk-shared-core into equivalent Java code in this repo. Use when the user mentions translating, porting, or syncing commits from sdk-shared-core.
4+
argument-hint: <commit-sha or range e.g. abc123 or abc123..def456>
5+
---
6+
7+
# Translate sdk-shared-core (Rust) to sdk-java
8+
9+
You are translating Rust code changes from [restatedev/sdk-shared-core](https://github.com/restatedev/sdk-shared-core) into equivalent Java code in this repository.
10+
11+
## Input
12+
13+
`$ARGUMENTS` contains one or more commit references from sdk-shared-core. These can be:
14+
- A single commit SHA (e.g., `abc123`)
15+
- Multiple commit SHAs separated by spaces (e.g., `abc123 def456`)
16+
- A commit range (e.g., `abc123..def456`)
17+
18+
## Step 1: Fetch the Rust diffs
19+
20+
For each commit or range, fetch the diff from GitHub:
21+
22+
- Single commit: `gh api repos/restatedev/sdk-shared-core/commits/<sha> --header "Accept: application/vnd.github.diff"`
23+
- Range: `gh api repos/restatedev/sdk-shared-core/compare/<base>...<head> --header "Accept: application/vnd.github.diff"`
24+
25+
Also fetch the commit message(s) for context:
26+
- `gh api repos/restatedev/sdk-shared-core/commits/<sha> --jq '.commit.message'`
27+
28+
## Step 2: Understand the changes
29+
30+
Before translating, analyze:
31+
1. What the Rust change does semantically (not just syntactically)
32+
2. Which Rust files/modules are affected
33+
3. What the commit message says about intent and motivation
34+
35+
## Step 3: Architectural mapping between the two codebases
36+
37+
The primary mapping target is:
38+
```
39+
sdk-core/src/main/java/dev/restate/sdk/core/statemachine/
40+
```
41+
42+
The two codebases implement the same state machine but with fundamentally different architectural patterns. Read the existing Java code before making changes. Don't blindly transliterate — adapt to the Java architecture while preserving semantics.
43+
44+
### Public interface: VM trait vs StateMachine interface
45+
46+
- **Rust**: `VM` trait in `src/lib.rs` with `sys_*` methods (e.g., `sys_state_get`, `sys_call`, `sys_run`)
47+
- **Java**: `StateMachine` interface in `StateMachine.java` with plain method names (e.g., `stateGet`, `call`, `run`)
48+
- Both return integer handles (`NotificationHandle` in Rust, `int` in Java) for async operations
49+
- Both have `doProgress()` / `do_progress()` as the core async driver
50+
- Both have `takeNotification()` / `take_notification()` for retrieving results
51+
52+
### State representation: enum variants vs class implementations
53+
54+
This is the **biggest architectural difference**.
55+
56+
**Rust**: States are variants of a single `State` enum in `src/vm/mod.rs`. Each variant carries its own data inline:
57+
```rust
58+
enum State {
59+
WaitingStart,
60+
WaitingReplayEntries { received_entries: u32, commands: VecDeque<RawMessage>, async_results: AsyncResultsState },
61+
Replaying { commands: VecDeque<RawMessage>, run_state: RunState, async_results: AsyncResultsState },
62+
Processing { processing_first_entry: bool, run_state: RunState, async_results: AsyncResultsState },
63+
Closed,
64+
}
65+
```
66+
67+
**Java**: States are classes implementing a sealed `State` interface. Each state class contains its own data as fields:
68+
- `WaitingStartState``WaitingReplayEntriesState``ReplayingState``ProcessingState``ClosedState`
69+
- The `State` interface declares default methods that throw `ProtocolException.badState()` for unsupported operations
70+
- Concrete state classes override the methods they support
71+
72+
### Transitions: structs with pattern matching vs methods on state classes
73+
74+
**Rust**: Transitions are **structs** (e.g., `NewMessage`, `SysGetState`, `DoProgress`) that implement `Transition<Context, Event>` or `TransitionAndReturn<Context, Event>` for `State`. Inside each impl, you **pattern match on the current state**:
75+
```rust
76+
impl TransitionAndReturn<Context, PopJournalEntry<M>> for State {
77+
fn transition_and_return(self, context: &mut Context, event) -> Result<(Self, Output), Error> {
78+
match self {
79+
State::Replaying { mut commands, run_state, async_results } => { ... }
80+
State::Processing { ... } => { ... }
81+
s => Err(s.as_unexpected_state(...))
82+
}
83+
}
84+
}
85+
```
86+
Transitions live in separate modules: `transitions/input.rs`, `transitions/journal.rs`, `transitions/async_results.rs`, `transitions/terminal.rs`.
87+
88+
**Java**: Transitions are **methods on the state classes themselves**. Each state class implements the transitions it supports:
89+
```java
90+
// In ReplayingState.java
91+
int processStateGetCommand(String key, StateContext ctx) { ... }
92+
93+
// In ProcessingState.java
94+
int processStateGetCommand(String key, StateContext ctx) { ... }
95+
```
96+
The `StateMachineImpl` delegates to the current state: `this.stateContext.getCurrentState().processStateGetCommand(key, this.stateContext)`.
97+
98+
**Key implication**: When a Rust commit adds or modifies a transition struct, in Java you need to add or modify the corresponding method across multiple state classes (typically `ReplayingState` and `ProcessingState`).
99+
100+
### Transition dispatch: do_transition vs StateHolder
101+
102+
**Rust**: `CoreVM.do_transition(event)` uses `mem::replace` to extract the current state, calls the transition, and stores the new state. Errors automatically send an error message and close output.
103+
104+
**Java**: `StateHolder.transition(newState)` simply swaps the current state reference. Error handling is done explicitly in state methods. `StateContext` acts as the central hub holding `StateHolder`, `Journal`, `EagerState`, etc.
105+
106+
### Context: Context struct vs StateContext class
107+
108+
- **Rust**: `Context` in `src/vm/context.rs` — holds `start_info`, `journal`, `output`, `eager_state`, `input_is_closed`
109+
- **Java**: `StateContext` in `StateContext.java` — holds `StateHolder`, `Journal`, `EagerState`, `StartInfo`, `inputClosed`, `outputSubscriber`
110+
- Both serve the same purpose: mutable state that persists across transitions
111+
112+
### Journal and async results
113+
114+
These are structurally very similar between both codebases:
115+
- `Journal` tracks `commandIndex`, `notificationIndex`, `completionIndex`, `signalIndex`
116+
- `AsyncResultsState` maps handles to `NotificationId`s, with `toProcess` queue and `ready` map
117+
- `RunState` tracks pending/executing run blocks
118+
- `EagerState` caches state values from StartMessage
119+
120+
### File mapping
121+
122+
| Rust file | Java file |
123+
|-----------|-----------|
124+
| `src/lib.rs` (VM trait) | `StateMachine.java` |
125+
| `src/vm/mod.rs` (CoreVM) | `StateMachineImpl.java` |
126+
| `src/vm/context.rs` (Context) | `StateContext.java`, `Journal.java`, `EagerState.java`, `StartInfo.java` |
127+
| `src/vm/context.rs` (AsyncResultsState) | `AsyncResultsState.java` |
128+
| `src/vm/context.rs` (RunState) | `RunState.java` |
129+
| `src/vm/transitions/input.rs` | Logic in `WaitingStartState.java`, `WaitingReplayEntriesState.java` |
130+
| `src/vm/transitions/journal.rs` | Methods across `ReplayingState.java` and `ProcessingState.java` |
131+
| `src/vm/transitions/async_results.rs` | Methods in `ReplayingState.java`, `ProcessingState.java`, `AsyncResultsState.java` |
132+
| `src/vm/transitions/terminal.rs` | `hitError()`/`hitSuspended()` methods on `State` interface |
133+
| `src/vm/errors.rs` | `ProtocolException.java` |
134+
| `src/service_protocol/` | `MessageDecoder.java`, `MessageEncoder.java`, `MessageType.java`, `ServiceProtocol.java` |
135+
136+
### Command processing patterns
137+
138+
Both codebases distinguish two kinds of commands:
139+
- **Non-completable** (fire-and-forget): e.g., `stateSet`, `stateClear` — no handle returned
140+
- **Completable** (returns a handle): e.g., `stateGet`, `call`, `run` — returns an int handle mapped to a NotificationId
141+
142+
In **Rust**, these are generic transitions like `SysNonCompletableEntry<M>` and `SysCompletableEntry<M>`.
143+
In **Java**, these are `processNonCompletableCommand()` and `processCompletableCommand()` methods on the state classes.
144+
145+
### Tests: builder VMTestCase vs 3-layer handler tests
146+
147+
**Rust** tests are low-level, directly exercising the VM:
148+
```rust
149+
VMTestCase::new()
150+
.input(StartMessage { known_entries: 1, .. })
151+
.input(input_entry_message(b"my-data"))
152+
.run(|vm| {
153+
let input = vm.sys_input().unwrap();
154+
vm.sys_write_output(NonEmptyValue::Success(input), ...).unwrap();
155+
vm.sys_end().unwrap();
156+
});
157+
```
158+
159+
**Java** tests use a 3-layer architecture:
160+
1. **Base test suites** (abstract classes like `StateTestSuite`, `CallTestSuite`) define test scenarios as `TestDefinition` streams
161+
2. **Concrete implementations** (e.g., `StateTest`) extend suites with actual handler code using the SDK's context API
162+
3. **Test executors** (`MockRequestResponse`, `MockBidiStream`) run each test in both buffered and streaming modes
163+
164+
Java test inputs are built with `ProtoUtils` helpers (`startMessage()`, `inputCmd()`, `getLazyStateCmd()`, etc.) and assertions use `AssertUtils`.
165+
166+
**Key implication**: When a Rust commit adds a new VM-level test, in Java you typically need to add a handler-level test in the appropriate test suite, not a direct state machine test.
167+
168+
## Step 4: Apply the translation
169+
170+
1. **Read the affected Java files first** before making changes
171+
2. Adapt the Rust change to Java's architecture (methods on state classes, not transition structs)
172+
3. If a Rust change doesn't apply to Java (borrow checker workarounds, Rust-specific memory management), skip it and note why
173+
4. **Run a build check** after translating: `./gradlew :sdk-core:compileJava`
174+
175+
## Step 5: Summary
176+
177+
After translating, provide:
178+
1. A summary of what was translated
179+
2. Any Rust changes that were skipped and why
180+
3. Any areas that need manual review or testing

CLAUDE.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# CLAUDE.md - Restate Java SDK
2+
3+
## Project Overview
4+
5+
This is the **Restate Java/Kotlin SDK** (`dev.restate`), a polyglot JVM SDK for building services on the [Restate](https://restate.dev) platform. It provides APIs for durable execution, virtual objects, workflows, and more.
6+
7+
## Build System
8+
9+
- **Gradle 9.3.0** with Kotlin DSL (`*.gradle.kts`)
10+
- **Java 17** toolchain (minimum target)
11+
- Dependencies managed via version catalog: `gradle/libs.versions.toml`
12+
- Convention plugins in `buildSrc/src/main/kotlin/`
13+
14+
### Key Commands/
15+
16+
```bash
17+
./gradlew build # Full build + tests
18+
./gradlew test # Run tests only
19+
./gradlew :sdk-core:test # Run a single module's tests
20+
./gradlew spotlessApply # Auto-format all code
21+
./gradlew spotlessCheck # Check formatting (runs in CI)
22+
./gradlew :sdk-aggregated-javadocs:javadoc # Generate Java docs
23+
./gradlew :dokkaHtmlMultiModule # Generate Kotlin docs
24+
```
25+
26+
### Before Submitting Changes
27+
28+
Always run `./gradlew spotlessApply` before committing — CI will fail on formatting violations.
29+
30+
## Code Style & Formatting
31+
32+
- **Java**: Google Java Format (enforced via Spotless)
33+
- **Kotlin**: ktfmt (enforced via Spotless)
34+
- **Gradle Kotlin DSL**: ktfmt (enforced via Spotless)
35+
- **License headers**: Required on all `.java`, `.kt`, and `.proto` files (template in `config/license-header`)
36+
37+
Do NOT manually format code — let Spotless handle it.
38+
39+
## Project Structure
40+
41+
### Core Modules
42+
- `sdk-common` — Shared interfaces and utilities (Java + Kotlin)
43+
- `sdk-api` — Public Java API (`dev.restate.sdk`)
44+
- `sdk-api-kotlin` — Public Kotlin API (`dev.restate.sdk.kotlin`)
45+
- `sdk-core` — Core state machine implementation (shaded jar)
46+
- `sdk-serde-jackson` — Jackson serialization support
47+
- `sdk-serde-kotlinx` — Kotlinx serialization support
48+
49+
### Deployment Modules
50+
- `sdk-http-vertx` — Vert.x HTTP server
51+
- `sdk-lambda` — AWS Lambda integration
52+
- `sdk-spring-boot` / `sdk-spring-boot-starter` — Spring Boot integration
53+
54+
### Code Generation
55+
- `sdk-api-gen` — Java annotation processor (Handlebars templates)
56+
- `sdk-api-kotlin-gen` — Kotlin Symbol Processing (KSP)
57+
- `sdk-api-gen-common` — Shared codegen utilities
58+
- Annotations: `@Service`, `@VirtualObject`, `@Workflow`
59+
60+
### Convenience Meta-Modules
61+
- `sdk-java-http`, `sdk-java-lambda`, `sdk-kotlin-http`, `sdk-kotlin-lambda`
62+
63+
### Infrastructure
64+
- `client` / `client-kotlin` — Admin clients
65+
- `admin-client` — Admin API client
66+
- `sdk-request-identity` — JWT/request signing
67+
- `sdk-testing` — TestContainers-based test utilities
68+
69+
### Testing
70+
- `test-services` — Test service implementations
71+
- `sdk-fake-api` — Mock API for unit tests
72+
73+
## Testing
74+
75+
- **Framework**: JUnit 5 (Jupiter) + AssertJ
76+
- **Integration tests**: Use TestContainers with Restate Docker image
77+
- **CI matrix**: Java 17, 21, 25
78+
- Tests live in `src/test/java` or `src/test/kotlin` within each module
79+
80+
## Key Conventions
81+
82+
- Package root: `dev.restate.sdk` (Java), `dev.restate.sdk.kotlin` (Kotlin)
83+
- Java compiler flag `-parameters` is enabled for annotation processing
84+
- Generated code goes to `build/generated/`
85+
- Proto files in `src/main/service-protocol/`
86+
- The `sdk-core` module uses Shadow plugin to shade dependencies
87+
88+
## Architecture Notes
89+
90+
- **`sdk-core`** is the most sensitive module — it contains the state machine that drives durable execution. Changes here require careful review.
91+
- **`sdk-api`** and **`sdk-api-kotlin`** are the public API surfaces. Changes here affect end users directly — maintain backward compatibility.
92+
- **`sdk-core` shading**: Dependencies are shaded via the Shadow plugin, so shaded classes won't appear at their original package paths in the final jar.
93+
- Proto files in `sdk-core/src/main/service-protocol/` define the Restate wire protocol — these come from an external spec and should not be modified without coordinating with the protocol definition.
94+
95+
## Things to Avoid
96+
97+
- Do NOT edit files under `build/generated/` — they are produced by annotation processors and KSP.
98+
- Do NOT manually edit formatting — run `./gradlew spotlessApply` instead.
99+
- Do NOT modify proto files without understanding the upstream protocol spec.

0 commit comments

Comments
 (0)