Prototype manifest v2 Java SDK - #19
Draft
eunomie wants to merge 7 commits into
Draft
Conversation
An inspiration prototype for Dagger manifest v2, the Java counterpart to dagger/go-sdk#36. Records the problem, why Java isolates the protocol change better than Go does, the proposed one-analyzer/two-backends approach, the four known gaps in the v2 interface (chief among them the missing error channel), what cannot be validated yet, and the implementation plan. Signed-off-by: Yves Brissaud <yves@dagger.io>
The manifest-v2 entrypoint needs the module's type definitions as Dang source at generate time, where today DaggerType only produces them as JavaPoet code for the runtime register() call. Give DaggerType a second rendering backend so both descriptions come from one model and cannot drift. Signed-off-by: Yves Brissaud <yves@dagger.io>
Manifest v2 replaces the ambient FunctionCall entrypoint with a call() field that hands a module its receiver, function name and arguments as JSON. Add the process side of that protocol: one JSON request on stdin, one JSON result on stdout. The protocol is hand-written SDK code rather than generated text, so a generated entrypoint only has to name its own dispatcher. Because call() has no error field, a failure can only be a non-zero exit; the envelope written to stderr is not part of the contract and nothing reads it, but it records what a real error channel would have to carry. Signed-off-by: Yves Brissaud <yves@dagger.io>
Manifest v2 has no ambient FunctionCall to read, so the dispatcher a module generates has to be callable directly. Promote the private invoke method to a public static daggerDispatch and give the generated main an engine-call mode that drives it over stdin and stdout. The v1 path is unchanged: dispatch(FunctionCall) still calls the same method, and main with no arguments still runs the ambient entrypoint. The engine-call branch exits before the telemetry block because there is no session to attach spans to -- a regression the manifest v2 interface forces, recorded in the design note. Signed-off-by: Yves Brissaud <yves@dagger.io>
Manifest v2 asks a module for its type definitions as data, through a Dang ModuleEntrypoint, instead of having the module register them over GraphQL at run time. The annotation processor already holds the model that answers that: give it a second backend that renders the same ModuleInfo as Dang. The result is written as a resource rather than a source file, so it lands beside the compiled classes instead of in the generated-sources tree that becomes the module's committed Java. Signed-off-by: Yves Brissaud <yves@dagger.io>
Add generateV2, which stages the Dang entrypoint the processor emits plus a manifest-v2 body naming it. It is deliberately not a @generate function: nothing can load either file yet, so running it as part of dagger generate would only commit a dead entrypoint into every managed module. The manifest goes to dagger-module.v2.toml rather than replacing dagger-module.toml. This SDK reads a module's schema by asking the engine to load it, and engine v1.0.0-beta.11 loads manifest v1 only, so replacing the manifest would leave the module unloadable and generateV2 unrepeatable. The dispatch check runs the generated jar in a plain Maven container that never had a Dagger session, which proves the exported dispatcher answers a call request without an engine. Signed-off-by: Yves Brissaud <yves@dagger.io>
CI is green on the change it describes. Signed-off-by: Yves Brissaud <yves@dagger.io>
eunomie
force-pushed
the
java-sdk-manifest-v2-prototype-lead-c62ff1c1
branch
from
September 3, 2026 15:30
227c047 to
12a2682
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
This is an inspiration prototype for manifest v2, the Java counterpart to dagger/go-sdk#36. It is not meant to merge as-is. The review points are the code-reuse boundary and the shape of the generated entrypoint.
It targets the interface as specified in dagger/dagger#14038 (
future/module-manifest-v2/spec.md, head75c7772), not the earlier informal draft that go-sdk#36 was written against. Where the two disagree, this follows the specification and the design note says so.Java is a smaller job than Go here, and that is the interesting part. Most of dagger/go-sdk#36 is relocation: the Go module analyzer lives in
dagger/dagger, so the Go SDK has to port the analyzer, its source maps, pragmas, codecs and static dispatcher into the SDK repo before it can emit anything. Java has no such move to make — its analyzer is an annotation processor that already lives here and already runs at build time. So this branch is an unusually clean test of the v2 protocol change on its own.Design
ModuleInfoandDaggerTypethat produce the runtimeregister()call now also render a DangModuleEntrypointwithtypesandcall.currentFunctionCallentrypoint with an exported, staticdaggerDispatch, plus anengine-callargument mode on the generatedmain.io.dagger.client.ModuleDispatcher) rather than generating a dispatch command per module, as Go must. It reads the request shape the specification documents:receiverValueandfnArgsas strings holding JSON text, decoded once.manifestVersion,name, and an[entrypoint]table withkind = "dang"— exactly the keys the engine accepts.v1.0.0-beta.11.The design note is in
hack/designs/done/2026-09-02-manifest-v2-prototype.md. It includes the complete rendered entrypoint for a sample module, regenerated verbatim from the code.What this says about the v2 interface
Four gaps, documented in full:
call(...): JSON!has no structured error channel. A failure is a GraphQL error, so the fact of a failure and its message survive; what disappears is the typedErrorvalue v1 builds fromfnCall.returnError— for Java that includesstdout,stderr,cmd,exitCodeandpathfrom a failedwithExec. The prototype writes those as a JSON envelope to stderr — explicitly not a mitigation, just a concrete record of what an error channel would have to carry. Telemetry initialisation goes the same way.@Objectwhose name matches the module. That satisfies the engine's current one-constructor limit for free, but it is a restriction v2 does not impose, and when the engine lifts the limit Java will not follow, because the constraint is in the analyzer.callis specified — original names throughout. An earlier reading treated this as an undocumented assumption; the spec settles it, and Java's dispatcher already matched it.types(): [TypeDef!]!.Current limits
ModuleEntrypointandWorkspace.cwd, neither of which is in a release.generateV2function, not wired intodagger generate, so no module commits a dead entrypoint.dagger-module.v2.toml, notdagger-module.toml: this SDK reads a module's schema by asking the engine to load it, so replacing the v1 manifest would make the module unloadable andgenerateV2unrepeatable.Validation
Same shape as dagger/go-sdk#36 — unit tests plus a generated fixture exercised directly, not a
dagger callthrough a released engine.dagger check: green.packager:unit-tests: 28 tests acrossDaggerTypeDangTest,ModuleDispatcherTestandDangEntrypointRendererTest.e-2-e:manifest-v-2-check: asserts the emitted Dang entrypoint (two fields, nomain, the spec'scallsignature, every type including the module's main class, exactly one constructor) and the manifest (kind = "dang", noengineVersion).e-2-e:manifest-v-2-dispatch-check: builds a fixture module's shaded jar and runsjava -jar <jar> engine-call < request.jsonin a plain Maven container with no Dagger session, sending the request shape the specification documents and asserting the JSON result. This is a real execution of the generated dispatcher.