| Area | Requirement |
|---|---|
| Controller | Java 21; Gradle wrapper included |
| Worker | CMake 3.20+, C++20 compiler, Protobuf, gRPC C++ |
| Database | PostgreSQL 17 via Docker Compose or an equivalent local instance |
| Smoke tests | Python 3 standard library; some failure-injection tests also require Linux process and network utilities |
The C++ worker is POSIX-specific. Develop it on Linux, macOS, or WSL with the required packages installed. The Java controller can run anywhere Java and PostgreSQL are available.
Start PostgreSQL from the repository root:
docker compose up -d
docker compose psThe checked-in development configuration uses:
database: forge
username: forge
password: forge
host port: 5433
These credentials are for an isolated local environment only.
cd controller
./gradlew clean test
./gradlew bootRunUseful focused commands:
./gradlew test --tests 'dev.forge.controller.task.TaskServiceTest'
./gradlew test --tests 'dev.forge.controller.grpc.WorkerRegistrationConcurrencyTest'
./gradlew test --tests 'dev.forge.controller.grpc.WorkerCommandStreamConcurrencyTest'The Spring context test connects to PostgreSQL and applies Flyway migrations. A connection failure at 127.0.0.1:5433 usually means Compose is not running, not that compilation failed.
Configure and build from the repository root:
cmake -S worker -B worker/build
cmake --build worker/build --parallel
./worker/build/forge-worker --controller=localhost:50051Reconfigure after changing CMakeLists.txt, the shared proto, compiler settings, or installed package locations. Generated C++ protobuf sources are written below worker/build/generated.
The worker accepts one optional argument:
--controller=<host:port> default: localhost:50051
Set FORGE_OUTBOX_DIR to isolate test data or place the durable outbox on a different filesystem:
FORGE_OUTBOX_DIR=/tmp/forge-outbox \
./worker/build/forge-worker --controller=localhost:50051With PostgreSQL, the controller, and at least one worker running:
python3 scripts/dag_smoke_test.py
python3 scripts/timeline_smoke_test.pyThese exercise the public API, task execution, dependencies, cancellation, retries, timeouts, worker reporting, and timelines.
The remaining scripts probe restart and failure boundaries:
restart_smoke_test.py,recovery_timeline_smoke_test.py, andworker_reconnect_smoke_test.pyguide a manual controller restart.outbox_*scripts verify persistence barriers, ordering, disconnect replay, and worker-crash replay.worker_*scripts verify heartbeat reconnect, session loss, fencing, takeover atomicity, cold-start authority, and retired-session persistence.
Warning
Failure-injection scripts can stop or kill local worker processes, manipulate networking, hold database locks, and write temporary state below /tmp. Read the script before running it and use an isolated development environment with exactly the worker topology it expects.
- Edit only
proto/forge.proto. - Preserve existing field numbers; never reuse a removed tag.
- Build both runtimes so Java and C++ bindings regenerate.
- Update controller stream handling and worker message handling together.
- Add a test for reconnect/replay behavior when the message participates in the durable stream.
- Update
docs/api.mdif the external contract changes.
The controller Gradle protobuf plugin reads ../proto. CMake uses the same file and places generated sources inside the build directory.
- Add a new, monotonically numbered Flyway migration under
controller/src/main/resources/db/migration. - Never edit an already-applied migration to change a deployed schema.
- Keep JPA mappings and database constraints aligned; Hibernate uses
ddl-auto=validate. - Prefer database constraints for invariants that must survive controller concurrency or restart.
- Test both a new database and an upgraded database when a migration transforms existing rows.
Forge separates logical task state from attempt state. When adding a transition:
- define which logical and physical states are valid before and after it;
- persist ownership or intent before external I/O;
- emit the matching execution event;
- make repeated or replayed worker events safe;
- consider controller restart between every durable write and network send;
- cover workflow cancellation, dependency release, automatic retry, and stale worker sessions.
Scheduled coordinators operate concurrently with REST requests and gRPC callbacks. Re-read current state inside the mutation boundary and preserve deterministic ordering where possible.
- Do not edit generated protobuf sources or checked build outputs.
- Keep Java packages under
dev.forge.controllerand public wire classes underdev.forge.proto. - Keep worker code C++20-compatible and preserve process-group cleanup for cancellation.
- Treat
workerIdas stable host identity andsessionIdas one process incarnation. - Update diagrams and lifecycle tables when a state, edge, RPC, or recovery window changes.
- Avoid logging task secrets or full command payloads in new diagnostic paths.
Before opening a pull request:
docker compose up -d
(cd controller && ./gradlew test)
cmake -S worker -B worker/build
cmake --build worker/build --parallel
python3 scripts/dag_smoke_test.py
python3 scripts/timeline_smoke_test.pyRun the relevant failure-injection script for changes to outbox durability, session authority, reconnect behavior, worker loss, or recovery. Then review git status and confirm that build directories, logs, outbox files, Python caches, and temporary state are not included.