Working plan to take the Java SDK RFC from zero to v1.0 GA at full L0–L3 parity with @adcp/sdk (TS) and adcp (Python).
This is a living document. Status updates land here; design decisions land in the RFC or in a follow-up RFC.
Decisions made post-RFC that supersede or refine the merged text. The table below is the index. When a row needs more space to explain, it gets its own specs/<topic>.md (per D16) — by default the table row is the spec.
| # | Decision | Resolution | Supersedes RFC |
|---|---|---|---|
| D1 | Repo home | Work in adcontextprotocol/adcp-sdk-java on feature branches → PR to main. |
RFC §Build, distribution, governance (named the repo, didn't specify cadence) |
| D2 | Java baseline | JDK 21 only. Drops the 17 floor. Consequences: no *Async mirror surface (virtual threads make sync API scale natively), no platform-thread fallback executor in WebhookEmitter, ScopedValue directly for UpstreamRecorder per-principal scope, no Spring Boot 2.7 long-tail support. |
RFC §Architecture / Java baseline (was "Java 17 LTS"); RFC §Async model (drops the 12-method *Async mirror) |
| D3 | Maven coordinates | Group org.adcontextprotocol. Artifacts: adcp (main), adcp-server, adcp-testing, adcp-spring-boot-starter, adcp-cli, adcp-reactor, adcp-mutiny, adcp-kotlin. Base Java package org.adcontextprotocol.adcp.*. Sub-packages by surface (e.g. .task, .server, .signing, .testing). |
RFC §Reference (named artifacts, didn't pin group / base package) |
| D4 | Protocol tarball Sigstore | Confirmed signed. Harness schema-fetcher runs cosign verify-blob per RFC, no checksum-only fallback path. |
Confirms RFC §Schema-bundle consumption assumption |
| D5 | Reference mock-server | The @adcp/sdk/mock-server package — same mock-server TS uses. See D8 for CI deployment shape. |
Specifies RFC §comply_test_controller "shared reference mock-server" |
| D6 | Maven Central publish cadence | Hold first publish until v0.3 alpha. v0.1 and v0.2 ship as local Gradle artifacts / SNAPSHOT only. Sonatype OSSRH namespace claim + GPG key setup still happens harness Week 1 (slow path; 1–5 business-day ticket) — we just don't push artifacts until v0.3. | RFC §Build, distribution, governance (RFC said "Maven Central alpha from v0.1") |
| D7 | javax/jakarta floor |
jakarta only, Spring Boot 3.x floor. Single adcp-spring-boot-starter artifact, no compat starter, no community 2.7 port. Spring Boot 2.7 OSS support ended Nov 2025; anyone still on it has a vendor relationship. |
Resolves RFC Open Question 6 in favor of option (a) |
| D8 | Mock-server CI deployment | Sidecar via npx adcp mock-server. GitHub Actions Node step installs a pinned @adcp/sdk version, backgrounds one mock-server per specialism on a port range, Java tests hit localhost. The pinned @adcp/sdk version is the CI conformance oracle for executable behavior, but never overrides D23's signed protocol bundle for schema/API truth. If they diverge, the release-blocking fix is to bump/patch the CI mock-server pin; a temporary exception requires WG maintainer approval in the same PR, an issue link, and an explicit statement that storyboard CI still passes or which compatibility assertion is intentionally skipped. Promote to a published Docker image if multi-specialism orchestration becomes unwieldy. |
Specifies D5's deployment |
| D9 | MCP Java SDK | io.modelcontextprotocol.sdk:mcp-core:1.1.2 + mcp-json-jackson2:1.1.2 at the core (not the mcp bundle artifact, which pulls jackson3). Used by adcp (caller) and adcp-server (agent). The Spring AI MCP SDK was donated to the modelcontextprotocol org in Feb 2025 and rebranded as the official Java SDK; current spring-ai-mcp-* artifacts are now thin Spring Boot wrappers on top of it — no parallel implementation. License: MIT (compatible, flagged for foundation position). Both prototype questions closed in specs/mcp-prototype-findings.md: (a) HttpServletStreamableServerTransportProvider in mcp-core is framework-neutral — no Jetty/Tomcat dep at compile time, adopter brings their own servlet container at runtime; (b) mcp-json-jackson2 and mcp-json-jackson3 are at identical 1.1.2 cadence with the same surface — we pin to jackson2 to match the rest of the SDK's Jackson tree. |
Resolves RFC Open Question 2 |
| D10 | A2A pre-1.0 type strategy | Depend directly on a2aproject/a2a-java at 1.0.0.CR1, skipping the in-tree fallback. CR1 (May 2026) shows a stable package layout — the Beta1→CR1 delta is bug fixes and dep bumps only, no API reshuffling. Pin to CR1 now; upgrade to 1.0.0 final (imminent) as a straight version bump. The original "keep types in-tree" plan is dropped: the in-tree fallback would have been throwaway code given how close 1.0 GA is. |
RFC default for Open Question 3 |
| D11 | TransitionGuard narrowing protection |
Guards declare which spec edges they touch. Conformance harness fails if a sandbox account's guards narrow any edge the storyboards exercise. Guards run after the spec edge check and can never relax a spec edge. | Resolves RFC Open Question 7 |
| D12 | Spring Security integration depth | Recipes-only at v1.0. No separate adcp-spring-boot-starter-security artifact. Auth models vary too much to pre-bake; recipes age better than autoconfig. Revisit if v0.3 design-partner feedback demands it. |
RFC default for Open Question 5 |
| D13 | Reactor + Mutiny adapters | At GA, not fast-follow. adcp-reactor and adcp-mutiny both ship in v1.0. WebFlux shops left to wrap the sync API would own that complexity forever and we'd lose the canonical surface. |
Confirms RFC §Async model |
| D14 | Kotlin co-release | At v1.0, thin extension artifact (adcp-kotlin). Coroutine suspend fun wrappers + DSL builders. Not a parallel SDK. Defer and Kotlin shops fork. |
Confirms RFC §Kotlin positioning |
| D15 | Spec-rev tracking cadence | ≤ 2 weeks from AdCP spec rev to a Java SDK release that consumes it. Same SLO TS and Python hold to. Slower than 2 weeks and JVM teams stop trusting the parity claim. The release must track the signed bundle pin for build/cache inputs and emit the release-precision adcp_version wire token after normalization. Before D6's first Maven Central publish, this SLO is satisfied by a tagged local/SNAPSHOT alpha; if 3.1 GA cuts between v0.2 and v0.3, cut an off-cycle v0.2.x local/SNAPSHOT rather than waiting for v0.3, unless the WG explicitly records a D15 exception. |
Specifies RFC §What kills adoption (item 1); refined by D23 |
| D16 | Design-decision filing convention | Match AdCP convention. Longer-form design / decision docs live in specs/<topic>.md (the same place the Java SDK RFC itself lives in the spec repo). The Confirmed-decisions table in this ROADMAP is the at-a-glance index; a decision spins up its own specs/ doc only when the table row isn't enough. No docs/adr/, no MADR template, no per-decision file by default. |
(No RFC §; sets a repo convention) |
| D17 | Branching model | Trunk-based. Short-lived feature branches → PR to main. Semver tags from main. No long-lived release branches. |
(Sets a repo convention; matches TS SDK) |
| D18 | Commits + changelog | Conventional Commits enforced via commitlint; Changesets for the CHANGELOG. Matches the TS SDK's workflow shape (same .changeset/ directory pattern) so humans and tools read both SDKs' release notes the same way. |
(Sets a repo convention; matches TS SDK) |
| D19 | Contributor IPR | Replicate the AAO IPR Bot pattern used by adcp / adcp-client / adcp-client-python / adcp-go. Contributors agree by commenting I have read the IPR Policy on their first PR; the AAO IPR Bot (a GitHub App) enforces via a required status check. Harness Week 1 actions: (1) foundation admin adds adcontextprotocol/adcp-sdk-java to the App's installation scope and the IPR_APP_ID / IPR_APP_PRIVATE_KEY org-secret scope; (2) add the ~15-line caller workflow at .github/workflows/ipr.yml invoking adcontextprotocol/adcp/.github/workflows/ipr-check-callable.yml@main; (3) CONTRIBUTING.md mirrors the adcp wording and links to IPR_POLICY.md in the spec repo. No DCO. No CLA. |
(Matches AAO standard) |
| D20 | Sonatype OSSRH namespace claim | Claim org.adcontextprotocol via DNS TXT verification. Maven Central confirms the namespace is unclaimed (zero artifacts today). Requires the foundation to add one TXT record to adcontextprotocol.org proving control. Sonatype ticket + DNS record both kicked off harness Week 1; first publish waits for v0.3 (per D6). |
Specifies D6 |
| D21 | Branch protection on main |
Required: 1 code-owner approving review, required CI checks (build, test, storyboard, IPR Policy / Signature), no force-push, no direct push, no admin bypass. Dependabot patch PRs auto-merge after green CI. Two-reviewer gate doesn't add safety with a founder pair; revisit when contributor base grows. |
(Sets a repo convention) |
| D22 | Multi-tenant signing keys | Signing key selection is tenant-aware, not just adcp_use-aware. The L1 signing SPI must select by purpose plus caller/resolved publisher tenant context, a JOSE-compatible pattern where one operator can publish multiple tenant keys on a single JWKS endpoint: same adcp_use, distinct kid per tenant. v0.2 freezes the explicit parameter set for SigningContext — AdcpUse use(), nullable tenant identity, nullable principal reference — and the shipped API is SigningContext-based with no single-arg SigningProvider.forUse(AdcpUse) short form. v0.3 wires tenant resolution from AccountStore / adagents.json into that selector. Longer-form shape lives in specs/signing-context.md. |
Refines RFC §L1 signing and §L2 multi-tenant principal resolution |
| D23 | AdCP 3.1 beta parity target | Track the signed protocol bundle as the source of truth; use TS/Python as implementation references. For this roadmap update the pinned target is 3.1.0-beta.5 (2026-05-26). If 3.1 GA cuts before the v0.1 codegen PR, bump to GA deliberately in that PR; otherwise beta.5 is the target. @adcp/sdk@8.1.0-beta.13 is aligned to beta.5; Python 6.3.0b4 is on beta.4 and is a reference, not a blocker. Java's local ADCP_VERSION remains 3.0.11; v0.1 must include wire-version negotiation, multi-bundle schema loading, and 3.1 beta/GA codegen coverage before claiming cross-SDK parity. |
Updates RFC parity baseline and D15 spec-rev SLO |
The RFC tracks @adcp/sdk 6.x and the first Java scaffold still pins ADCP_VERSION=3.0.11. Current upstream parity moved again. Precedence for Java work is: signed protocol bundle first, protocol release notes/spec docs second, pinned mock-server behavior for CI conformance third, TS/Python implementation behavior fourth. A mock-server mismatch blocks release by forcing a mock-server pin/patch decision; it does not redefine the protocol.
| Source | Version checked | Java parity read |
|---|---|---|
| AdCP protocol bundle | 3.1.0-beta.5 |
Java is behind on schema pin and 3.1 compliance bundle coverage. The next codegen bump should target beta.5 unless 3.1 GA has cut by then. |
@adcp/sdk (TS) |
8.1.0-beta.13, adcp_version=3.1.0-beta.5 |
Best reference for 3.1 caller/server ergonomics, conformance runner behavior, canonical-format helpers, cache-scope guards, and beta migration shims. Aligned to the target bundle. |
adcp (Python) |
6.3.0b4, packaged ADCP_VERSION=3.1.0-beta.4 |
Best reference for Pydantic/server semantics, request-scoped capabilities, externally-managed webhook signing capabilities, resolver hardening, and version-routed validation. One beta behind the target bundle, so use for API shape, not as the normative schema source. |
adcp-go |
v1.x (dev) | Still useful as a third-language sanity check, especially for typed version pins and signer/verifier shapes. |
Net: Java is not fully up to date. The roadmap now treats 3.1 beta parity as a v0.1 input, not a post-v0.1 cleanup. The remaining gap is not just generated types: Java needs version negotiation, multi-bundle validators, cache-scope semantics, 3.1 compliance bundle execution, and a handful of SDK helper surfaces that TS/Python have already exposed.
Read from the TS and Python SDK changelogs plus the AdCP 3.1 beta release notes. Each delta lists the Java track it folds into. None invalidate the RFC; all bump scope inside existing tracks.
AdCP 3.1 protocol surfaces
- Wire-version negotiation (
adcp_version,supported_versions, response echo,VERSION_UNSUPPORTED). Keep two tiers distinct: bundle pin (3.1.0-beta.5, full semver, build/cache key) and release-precision wire token normalized per spec (3.1for stable releases; beta wire tokens round-trip what the seller emits after normalizing full prerelease pins, e.g.3.1.0-beta.5→3.1-beta.5). Current Java code only models the stablemajor.minorform, so v0.1 must expandAdcpVersionto expose both.bundle()and.wire()before 3.1 beta traffic is supported. Cold-start callers send the SDK default wire token for the target bundle, then honorVERSION_UNSUPPORTED.supported_versionsif the seller asks for downgrade/upgrade. Java must emit bothadcp_versionand legacyadcp_major_versionthrough 3.x, reject major-level disagreement between the two, pick response validators by served version, route unversioned legacy traffic to3.0, and collapse prerelease handling back to stable3.1when 3.1 GA cuts. →transport+codegen+testing. - Multi-bundle schema loading. TS ships 3.0 and 3.1-beta caches and can run external 3.0 compliance bundles from a 3.1-beta runner. Python ships 2.5, 3.0, and 3.1 beta caches. Java needs the same loader shape, not a single
ADCP_VERSIONglobal. →codegen+testing. - Protocol envelope status is required. Every task response, including sync reads like
get_adcp_capabilities, carries top-level envelopestatus. Server helpers should stamp it centrally; domain payload aliases should not force adopter handlers to return envelope fields by hand. →transport+codegen. - Media-buy lifecycle status split.
create_media_buyandupdate_media_buysuccess payloads usemedia_buy_status; legacy top-level bodystatusis deprecated becausestatusnow belongs to the task envelope. Java must read canonical first, tolerate legacy during the 3.1 window, and avoid confusing A2A task artifact status with domain status. →codegen+transport+testing. - Governance status renames. Experimental governance responses moved root body status to
verdict/outcome_state, and audit entries useentries[].verdict. →codegen. - Open error-code decoding. As SDK forward-compatibility practice, receivers should not fail closed on unknown
error.code; classify fromrecoveryand default conservatively when absent. Java generated enums need an unknown-value strategy, not a hard enum parse failure. →codegen+transport. - Advisory
errors[]on success payloads.STALE_RESPONSE, canonical-format projection warnings, pixel-tracker downgrade/upgrade warnings, and similar advisories ride in payloaderrors[]while transport/task success remains success. Java callers and validators must not promote advisory payload errors to thrown failures. →transport+testing. - Universal request
idempotency_key. 3.1 read tools accept every-request envelope fields and the compliance suite probes this. Java request builders and MCP wrapper validation must tolerate and emit envelope fields on every task request, not only write calls. →codegen+transport+async-l3. - Webhook token round-trip.
McpWebhookPayload.tokenis typed and must echo through webhook dispatch/receipt paths. →signing+async-l3. - Endpoint proof-of-control. Durable account-level webhook configs require proof-of-control semantics and stable
subscriber_idreplace/upsert behavior. →async-l3+multitenant.
3.1 buying, catalog, and signal surface
- Wholesale feed mirroring.
get_products/get_signalssupportwholesale_feed_version,if_wholesale_feed_version, optional pricing version,unchanged, and requiredcache_scope(public/account). Account-level wholesale feed webhooks deliverproduct.*,signal.*, andwholesale_feed.bulk_changewith repair through read tools. →multitenant+async-l3+testing. - Cache-scope guardrails. TS now fails closed when server payloads omit
cache_scopeon product responses. Java server builders should make this hard to omit on account-scoped product/signal responses, because cache-scope is the safety property that prevents leaking account overlays through shared caches. →transport+multitenant. - Wholesale signals and
SignalRef.get_signals discovery_mode=wholesale, canonicalsignal_refidentities, product-local / data-provider / signal-source scopes, selectablesignal_targeting_options,signal_targeting_rules, and package-leveltargeting_overlay.signal_targeting_groups. Legacysignal_idremains compatibility input. →codegen+multitenant. - Canonical creative formats.
format_options[],format_option_refs,format_option_id,v1_format_ref, named canonical format helpers, v1↔v2 projection, pixel-tracker advisory downgrades, and cache-backed canonical registries. The beta.2capability_idswrite path was removed before beta.5; Java should model the beta.5format_option_*names from the start. →codegen+transport. - Public placement catalogs.
adagents.jsoncan publish placement catalogs and publisher-scopedplacement_refs; seller-private routing stays out of public placement schemas. →multitenant. - Vendor-attested measurement.
vendor_metricoptimization goals, per-productvendor_metric_optimization, reporting-coherence preconditions, and compliance coverage. →codegen+testing. - Delivery and billing finality.
reach_window,viewability.viewed_seconds, windowed pull recovery, row-level delivery finality,report_usagefinality, andBILLING_OUT_OF_BANDas an error-code surface. →codegen+testing. - Action discovery.
allowed_actions[],available_actions[], finer media-buy action enum values,ACTION_NOT_ALLOWED, and helper-level request decomposition in TS/Python. Java should expose typed helpers aroundupdate_media_buymutations instead of forcing every adopter to re-parse action intent. →lifecycle+transport.
Cross-SDK helper surface to match
TS/Python helper names are references, not Java naming requirements. Java idioms win: *Request builders, records/sealed types, instance or namespaced helpers where clearer than free functions, and @Nullable rather than Optional on public model fields.
- TS
@adcp/sdk@8.1helper additions. Root exports now include canonical creative format helpers, format projection/write-side helpers,ensureGetProductsCacheScope()/validateGetProductsCacheScope(),parseWholesaleFeedWebhookNotification()/normalizeWholesaleFeedWebhookNotification(), signal discovery helpers,decomposeUpdateMediaBuy()/assertUpdateMediaBuyAllowed(), per-tool type slices, SSRF-safe networking helpers, typed server*Payloadaliases, and 3.1 compliance/cache selection in the runner. Java equivalents belong in the mainadcp,adcp-server, andadcp-testingartifacts rather than new artifacts. - Python
adcp@6.xhelper additions. Python now has version-routed validation, 2.5/3.0/3.1 beta schema caches, canonical-format projection, webhook proof-of-control helpers, wholesale feed sender, request-scoped capabilities hooks, unknown-field policy and hook composition, media-buy version handling/update actions, externally managed webhook signing capabilities, and permissive property resolution. Java server APIs should mirror the capability/hook seams even if implementation names differ.
Authentication / discovery
AuthenticationRequiredError.challenge+probeAuthChallenge(7.2.0). ParsesWWW-Authenticateon non-Bearer 401s; the parsed{ scheme, realm?, scope?, error?, error_description? }rides on the error so consumers can branch on auth scheme. Wired into MCP/A2A discovery and the A2A in-flight 401 path. →transport+docs.- HTTP Basic auth (
auth: { type: 'basic', … },--auth-scheme bearer|basic, RFC 7617) (7.2.0). Adopters fronted by Apigee / Kong / AWS API GW / nginxauth_basicwere unreachable on bearer-only. Java must accept user:pass auth config, validate at register-time, and injectAuthorization: Basicvia header merging (mutually exclusive with OAuth/bearer). →transport+cli. resolveAgentProperties/listAgentPropertyMap/canonicalizeAgentUrl+AdAgentsPublisherPropertySelector(7.2.0). Per-entryauthorization_typedispatch overadagents.json(property_ids/property_tags/inline_properties/publisher_properties/signal_ids/signal_tags). Fixes a cross-SDK divergence vs. Python. Java must match Python's_resolve_agent_properties, not the pre-fix TS behavior. →multitenant.validateAdAgentswith ads.txtMANAGERDOMAINone-hop fallback (7.2.0). New top-level entrypoint withDiscoveryMethod/AdAgentsValidationResulttypes. One hop only;publisher → publishercycles rejected;#noagentshonored. →multitenant.MCPOAuthProviderallowHttp+ RFC 9728 resource-URL handling (6.19.1). Local dev pattern. →transport+docs.
Conformance / storyboard surface
storyboards_missing_toolsvsstoryboards_not_applicablesplit (7.0.0, breaking on TS).ComplianceResultshape changed. Java conformance harness must emit the split from day one. →testing.AdcpErrorInfo.issues[]as first-class field (7.0.0) — percore/error.json3.0 GA. →codegen.RunnerNotice/NoticeCode/NoticeSeverity(7.1.0) — stablecodevalues so CI gates and JUnit consumers don't parse proseskip.detailstrings.StoryboardResult.noticesalways-present;ComplianceResult.noticesoptional and deduped bycode. →testing.ResponseSchemaValidationErrortyped error class (7.1.0) — attributes Zod schema rejects toresponse_schemaand short-circuits step-scope invariants. Java equivalent: typed error from Jackson + json-schema-validator that the storyboard runner can branch on. →codegen+testing.parallel_dispatchstep (7.0.0) — fans out N concurrent dispatches against the same agent (Promise.allshape), grades the cross-response set; drives the AdCP 3.1 concurrent-retry / first-insert-wins phase of the idempotency storyboard. New check kinds (cross_response_*). In Java this maps to virtual threads on 21+ / bounded executor on 17–20, plus structured concurrency for the barrier. →testing+async-l3.
Idempotency / async wire codes
IDEMPOTENCY_IN_FLIGHT(AdCP 3.1 wire code) (7.0.0) — replaces legacySERVICE_UNAVAILABLEfor the in-flight branch;recovery: transient,retry_afterderived from claim age (short for fresh, longer for slow handlers, capped at 5s).IdempotencyCheckResult.kind === 'in-flight'carriesretryAfterSeconds. Java idempotency middleware must emit this and Java callers must auto-retry ontransient + retry_after. →async-l3.
comply_test_controller / requirement autodetect
webhook_receiverautodetect from storyboard token presence (7.0.0). →comply.request_signer+oauth_metadataautodetect, pre-emptsoauth_discoverycascade-skip (7.1.0). →comply.account-discoveryspec-conformance gate (6.17.0) — gates aroundget_adcp_capabilities/list_accounts/sync_accountsadvertisement. →comply+multitenant.- Per-storyboard
requires:gate (controller/seeded_state/real_wire) +--asserts-seeded-stateflag (6.17.0). →testing. - Detailed skip-cause taxonomy (
RunnerDetailedSkipReason) (6.17.0). →testing.
upstream-recorder — new artifact surface (not on the RFC)
@adcp/sdk/upstream-recorder(added in 7.x). Producer-side reference middleware for the AdCPupstream_trafficstoryboard check (spec PR #3816). Wraps the adapter's HTTP layer, redacts at record time (plaintext secrets never sit in memory), enforces per-principal isolation (spec HIGH security requirement), exposesquery()that maps onto the controller'sUpstreamTrafficSuccessshape. Sandbox-only by default; production-disabled is a no-op zero-overhead path.- Java fit: lands in
adcp-serverasUpstreamRecorderSPI. Per-principal scoping usesScopedValue(Java 21 baseline; see Confirmed decisions). HTTP wrapper integrates at the same boundary as outbound signing. → new sub-scope inasync-l3(the recorder co-locates with webhook outbound HTTP). The RFC'scomply_test_controllersurface gains aquery_upstream_trafficscenario served from the recorder.
Codegen surface tightening (TS-only output shape, applies to Java codegen too)
Format.assetsnamed slot unions (IndividualAssetSlot/GroupAssetSlot/FormatAssetSlot) withasset_typediscriminator + per-slotrequirements(6.19.0). Java codegen must emit equivalent sealed interfaces with the discriminator pattern. →codegen.CreativeBuilderPlatformdoesn't advertise missing tools intools/list(7.0.0) — the platform doesn't lie about its capabilities. Java server-side handler must conditionally advertise tools based on adopter implementation. →transport.
Security / SSRF posture (cross-cutting hardening)
-
The 6.16–6.17 line added a cross-cutting SSRF migration covering
detectProtocol,discoverAgentProfile,fetchAdAgentsJsonFromUrl, the network-consistency-checker, the property-crawler, and the buyer-side discovery path. The baseline is: resolve DNS once, validate the full address set against address-guards, pin the connect to the first validated address (undici interceptor),redirect: 'manual', 4 KiB body cap on probes. Java SDK must match this baseline on every outbound discovery call — not as a v1.x hardening pass, as v0.1 baseline. JDKHttpClientdoesn't pin connect-addresses natively; Java needs a customAuthenticator/Selectorshim or the equivalent at theSocketChannelboundary. → cross-cutting concern, lives intransportwith its own design doc before contributor pickup. -
tasks/cancelfire-and-forget on buyer abort (6.16.0). Aborted poll must POST a real-UUIDtasks/cancelwithAbortSignal.timeout(5000), silently catch rejection (an aborted buyer must not have observability dependencies on cancel success).signed-requestssellers will 401 unsigned cancels — so this path threads through outbound signing. →transport+signing.
The 7.x / 8.x / 3.1 beta deltas don't move the M+12 GA line, but they do tighten the early release gates:
- v0.1 release gate adds: D23 target bundle ingestion (
3.1.0-beta.5, or 3.1 GA if cut before the codegen PR), multi-bundle validator selection, wireadcp_versionsupport for stable and prerelease tokens, envelopestatusstamping, SSRF baseline (DNS pin, address-guards, redirect:manual, body cap) on all discovery probes,WWW-Authenticate-aware error envelope, HTTP Basic config support, and thestoryboards_missing_tools/storyboards_not_applicable/steps_not_selectedsplit in runner output. This accepts a realistic v0.1 slip from M+2 to M+3 rather than deferring the security/auth baseline. - v0.2 gate adds: typed webhook token echo, endpoint proof-of-control foundations, and enough signing metadata to support externally managed webhook-signing capabilities without assuming the SDK owns every key.
- v0.3 gate adds:
IDEMPOTENCY_IN_FLIGHTwire code with claim-age-derivedretry_aftercap, universal requestidempotency_keyhandling,resolveAgentProperties/validateAdAgents/MANAGERDOMAINfallback, wholesale feed versioning/cache-scope semantics, canonical format projection helpers, andSignalRef/ product-scoped signal targeting. - v0.4 gate adds: durable account-level webhook configs with proof-of-control, wholesale feed webhook parsing/emission,
upstream-recorderSPI,query_upstream_trafficcontroller scenario,parallel_dispatchstoryboard step, fullRunnerNoticetaxonomy, advisoryerrors[]handling, and the 3.1 compliance storyboard additions.
Open question from the user: should we (the founder pair) build a harness before opening tracks to contributors?
Recommendation: yes, ~2 weeks of scaffold, then open tracks. Empty repos lose contributors. A skeleton with "your environment compiles, your tests run, here's where to start" loop is the difference between a track claim turning into a PR vs. turning into a Slack thread.
Hard line: scaffold the build, leave the rooms empty. Don't pre-build L1 / L2 / L3 surface — that locks in design before the contributors who'll own those tracks weigh in.
| Item | Why | Builds toward |
|---|---|---|
Gradle multi-module skeleton (5 published artifacts + 2 bridge modules adcp-reactor / adcp-mutiny as empty stubs) |
Stable multi-module graph from day one; package names locked; contributors don't fight settings.gradle.kts reviews |
infra |
Schema-bundle fetcher Gradle task: download {version}.tgz, cosign verify-blob, extract to build dir |
Codegen has something to point at; SSRF/signing posture established before any HTTP code lands | infra |
Codegen MVP: emit records + builder records for one or two request/response pairs (e.g. GetProductsRequest / GetProductsResponse) |
Proves the generator architecture, locks in the *Request/*Response naming invariant, gives contributors real Java to import. Full coverage stays in codegen. |
codegen |
Prototype the two open MCP-SDK questions on io.modelcontextprotocol.sdk:mcp:1.1.2 (per D9): can mcp-core's servlet streamable-HTTP server transport run without Jetty/Tomcat? Is mcp-json-jackson2 feature-equivalent to the Jackson 3 variant? |
D9 picked the SDK; these two are the only unresolved bits before transport opens for claim. |
transport |
Sonatype OSSRH namespace claim for org.adcontextprotocol + foundation GPG key + key-server publication |
Slow-path ticket (1–5 business days); start Week 1 even though first publish waits for v0.3 (per D6). Don't block v0.3 on a stalled OSSRH ticket. | infra |
SSRF-safe HttpClient wrapper skeleton (DNS pin, address-guards, redirect:manual, body cap) |
Baseline TS/Python security posture. JDK HttpClient doesn't pin natively; this needs a design doc + skeleton before contributors touch outbound HTTP. |
transport |
Storyboard CI gate shell: GitHub Actions on JDK 21, runs the runner against the @adcp/sdk/mock-server, even if the runner currently asserts only "we reached the server" |
The v0.1 release gate is "storyboards green in CI." Standing it up empty and having it pass keeps contributors honest as L0 fills in — every PR is measured against the gate. | infra + testing |
Repo conventions: CONTRIBUTING.md (track-claim flow + IPR pointer per D19), .github/ISSUE_TEMPLATE/track-claim.md, PR template, CLAUDE.md for agent contributors |
The track-claim issue template is the actual contributor onboarding doc | docs |
AAO IPR caller workflow at .github/workflows/ipr.yml (per D19) + foundation admin installs the IPR Bot on this repo + adds it to the IPR_APP_ID / IPR_APP_PRIVATE_KEY org-secret scope |
Required-status check IPR Policy / Signature is the gate on every PR (per D21) — must be working before contributor PRs arrive |
docs + infra |
DNS TXT record on adcontextprotocol.org for Sonatype namespace verification (per D20) |
Foundation-admin action; pairs with the OSSRH ticket. Without it the ticket stalls. | infra |
Branch protection on main per D21 (required reviews, required checks, no force-push, no admin bypass) |
Locks the trunk-based model (D17) before tracks open | infra |
| Commitlint + Changesets wiring (per D18) | Adopter expectations are set by @adcp/sdk's release notes; matching the workflow shape means humans and tools read both SDKs' changelogs the same way |
infra + docs |
These look tempting to "get started on" but pre-building them locks in design that should be a track owner's call:
- L1 RFC 9421 signing — too much spec surface; lives in
signing. - Full type generation —
codegen's job once MVP is proven. - Spring Boot starter — downstream, blocks on L1/L2/L3 surface.
- Account store / idempotency / webhook code — design-heavy; needs the track owner's voice.
- Lifecycle YAML coordination — depends on TS/Python maintainer buy-in (RFC Decision 6).
- Week 1 (founder pair): Gradle skeleton, schema fetcher, codegen MVP, repo conventions, confirmed-decision index, CI shell.
- Week 2 (founder pair + advisors): MCP SDK pick recorded in the decision index. SSRF wrapper skeleton +
specs/<topic>.mddesign doc. First storyboard CI run green-against-empty. Open the first 3–4 track-claim issues publicly. - Week 3+: Contributors arrive against a repo where
./gradlew checkpasses and CI tells them whether their PR broke conformance. Tracks land in dependency order.
This means the founder pair owns the infra track end-to-end and the first slice of codegen and transport. Everything else opens for claim once the harness is green.
The roadmap below has two axes:
- Milestones (v0.1 → v1.0) — vertical slices tied to the RFC roadmap. Each milestone has a date target (M+N from project kickoff) and a release gate.
- Tracks — parallel workstreams that contributors can claim independently. A track spans multiple milestones.
Contributors claim a track, not a milestone. Tracks have explicit dependencies; if track A blocks track B at milestone N, that's called out so we don't fail to sequence.
Each track entry has:
- Scope — what's in.
- Out of scope — what isn't, to keep the track bounded.
- Depends on — tracks that must land first.
- Size — rough person-month estimate (eng-months of focused work, not calendar time).
- Owner —
TBDuntil a contributor claims it. Claim by opening an issue with the track ID.
| Milestone | Target | Release gate |
|---|---|---|
| v0.1 alpha | M+3 | D23 target bundle compiles (3.1.0-beta.5, or 3.1 GA if cut before the codegen PR).Wire-version negotiation works for stable and prerelease tokens, including adcp_version / adcp_major_version major mismatch rejection and VERSION_UNSUPPORTED data with supported_versions plus deprecated supported_majors through 3.x.Multi-bundle validators can serve 3.0 and 3.1 traffic. SSRF/auth discovery baseline lands. Storyboards green against reference mock-server in CI. Local Gradle artifacts only (per D6 — first Maven Central publish at v0.3). |
| v0.2 alpha | M+4 | L1: RFC 9421 signing/verification, AWS+GCP KMS providers (lazy-init, tenant-aware per-adcp_use key selector shape per D22; tenant resolver wiring lands in v0.3), webhook signing, typed webhook token / proof-of-control foundations |
| v0.3 alpha | M+6 | L2 + partial L3: account store, idempotency, async tasks, wholesale feed cache-scope semantics, canonical-format / signal-targeting helpers, Spring Boot starter alpha. First Maven Central publish (per D6). |
| v0.4 beta | M+9 | Full L3: transition validators, webhook emission, wholesale feed webhooks, comply_test_controller, A2A transport (implemented on a2a-java 1.0.0.CR1), 3.1 compliance bundle parity |
| v1.0 GA | M+12 | L0–L3 parity, Reactor + Mutiny adapters, Kotlin co-release, Maven Central GA |
The RFC's M+12 target is the realistic line. Pre-committing M+9 and slipping is worse than committing M+12 and beating it. Slippage concentrates on: MCP Java SDK churn, RFC 9421 canonicalization edge cases, shared lifecycle YAML coordination, Spring Boot starter scope creep.
ID: infra | Owner: TBD | Size: 1.0 person-month across the year
Scope:
- Gradle multi-module skeleton matching the 5 artifacts (
adcp,adcp-server,adcp-testing,adcp-spring-boot-starter,adcp-cli). adcp-reactorandadcp-mutinymodules wired from the start (empty untilasync-bridges) so the multi-module graph is stable.Automatic-Module-Nameset on every JAR manifest.- Gradle reproducible-jar config, lockfiles checked in.
- Codegen Gradle task that downloads the protocol tarball, verifies with
cosign verify-blob, extracts schemas, hands off tocodegen. - Sonatype OSSRH namespace + GPG key set up harness Week 1; first Maven Central publish at v0.3 (per D6). Sigstore migration tracked as a follow-up.
- GitHub Actions on JDK 21, run storyboard CI against
@adcp/sdk/mock-server(the v0.1 gate; storyboard runner fromtestingplugs in). - JavaDoc + sources jars on every release.
Out of scope: GraalVM native-image (post-v1.0), JPMS modules (opt-in only).
Depends on: nothing. This track unblocks everything else and should land in week 1.
Milestone targets: v0.1 needs Gradle multi-module + CI on JDK 21 + green storyboard runner against the sidecar npx adcp mock-server. v0.3 adds the first Maven Central publish (per D6). Sigstore signing migration can land any time before v1.0.
ID: codegen | Owner: TBD | Size: 2.5 person-months
Scope:
- Custom codegen on Eclipse JDT or JavaPoet, emitting Java records for value/response types and builder-records for request types.
- Generator invariant:
*Requesttypes always have builders;*Responsetypes are records and never do (RFC §Type generation). - Generator invariant: open protocol vocabularies emit an unknown-safe shape (
sealed Known<T> / Unknown(rawValue)by default), not strict enums. Seespecs/codegen-open-enums.md. - Polymorphic envelope handling (Jackson
@JsonTypeInfo/@JsonSubTypes). x-adcp-*annotation post-processors mirroringscripts/generate-types.tsinadcp-client.- Version pinning support (
adcp-v2-5co-existence namespace): generates frozen v2.5.1 types underorg.adcontextprotocol.adcp.generated.v2_5.*alongside primary v3.0 and v3.1 namespaces. All namespaces coexist on the classpath. v2.5.1 schemas fetched from the GitHub source archive (adcontextprotocol/adcp@v2.5.1/static/schemas/source) since pre-3.0 bundles were never published to the CDN. - Multi-bundle schema registry: runtime validator lookup by served wire version (
3.0,3.1, and 3.1 beta tokens), with the full bundle pin stored separately from the wire value.ADCP_VERSIONcannot remain the only schema selector. - Unknown-safe enums for open protocol vocabularies, especially
ErrorCode, media-buy / creative status values,recovery, and action-discovery enums; strict Java enums are acceptable only where the spec vocabulary is intentionally closed. - Generated response payload aliases for server handlers, so framework adapters stamp protocol-envelope fields centrally instead of making adopter code return top-level
status,adcp_version, and related envelope fields by hand. - 3.1 shape changes: required envelope
status,media_buy_status, governanceverdict/outcome_state,SignalRef,format_option_refs, product-scoped signal targeting groups, wholesale feed version/cache fields, delivery/billing finality, vendor-metric goals, action-discovery enums, and typed webhook token payloads. - JSpecify
@Nullableannotations on every public type. NoOptional<T>returns. - Schema validator wrapper around
com.networknt:json-schema-validator. - Schema-bundle accessor (runtime, resources jar; build-time loader lives in
infra). - Agent-surface emission helpers for MCP and A2A. Generated MCP tool input descriptions consume spec text, A2A skill IDs follow a cross-SDK convention, and universal
idempotency_keyfeedsidempotentHint: truewhere MCP supports it. Seespecs/agent-surface-emission.md.
Out of scope: Kotlin source generation (handled by kotlin).
Depends on: infra (codegen Gradle task hookpoint).
Milestone targets: v0.1 needs full generated type coverage for the L0 surface, D23 target-bundle coverage (3.1.0-beta.5, or 3.1 GA if it cuts first), multi-bundle validator selection, and stable/prerelease wire version normalization wired into transport.
ID: transport | Owner: @MichielDean (#17) | Size: 2.0 person-months
Scope:
- MCP: depend on
io.modelcontextprotocol.sdk:mcppinned1.1.2(per D9). Used by bothadcp(caller) andadcp-server(agent). Plan a deliberate 2.x migration PR ~6 months out (the 2.0 line removes sealed interfaces from message types, replacesJsonSchemawithMap, flips the tool-input-validation default, removes server-transport builder methods). License is MIT — flagged for foundation position. Two open prototype questions land harness Week 1: whether the servlet-based streamable-HTTP server transport works without pulling Jetty/Tomcat, and whethermcp-json-jackson2is feature-equivalent to the Jackson 3 module. - A2A (implemented): caller-side
A2aConnectionManager+A2aCallerinadcp, plus server-sideA2aAgentExecutor+A2aServerBuilder+A2aServletinadcp-server, now ship on upstreama2aproject/a2a-javapinned1.0.0.CR1(per D10). No in-tree fallback ships. - A2A version bump path: upgrade from
1.0.0.CR1to1.0.0final is a straight version bump once the upstream GA tag is cut. - HTTP transport on
java.net.http.HttpClient. No third-party HTTP client in the core. - Jackson
ObjectMapperwithStreamReadConstraints/StreamWriteConstraintswidened to AdCP-shaped defaults (RFC §JSON). - Wire
adcp_versionon every request/response, accepting and echoing release-precision stable and prerelease values normalized per spec while storing the full bundle pin separately from the wire token. Keep the legacyadcp_major_versionmirror through 3.x. Server dispatch validates requested version before handler execution, rejects major-leveladcp_version/adcp_major_versiondisagreement, and returns typedVERSION_UNSUPPORTEDwithsupported_versionsplus deprecatedsupported_majorsthrough 3.x. - Central response-envelope stamping for sync success, submitted, failed, and advisory-success responses. The seam is a
ResponseEnvelope<P>record materialized by the server adapter after adopter handler payloads return and before signing/serialization; outbound signing canonicalizes the stamped envelope. Payloaderrors[]advisories likeSTALE_RESPONSEmust not be thrown as transport failures. - Auth/discovery parity with TS/Python: Basic/Bearer config,
WWW-Authenticatechallenge parsing, private-agent-card auth forwarding, and the standard A2A Agent Card path. - Server handler payload aliases and unknown-field policy hooks matching the Python/TS ergonomics: adopters return domain payloads; the framework owns envelope fields and strictness policy.
- No
*Asyncmirror methods. With JDK 21 as baseline, virtual threads make the sync API scale natively; the RFC's 12-method*Asyncmirror surface is dropped (see Confirmed decisions). Adopters who explicitly wantCompletableFuturewrap individual calls themselves.
Out of scope: OkHttp / Apache HttpClient 5 adapters (post-v1.0 on demand).
Depends on: codegen for the request/response types.
Milestone targets: v0.1 needs MCP transport plus version negotiation, envelope stamping, SSRF baseline on discovery probes, WWW-Authenticate challenge parsing, Basic auth config, and private-agent-card auth forwarding. v0.4 has A2A transport implemented on upstream a2a-java 1.0.0.CR1; moving to 1.0.0 final is a straight version bump.
ID: signing | Owner: TBD | Size: 1.5 person-months
Scope:
- Hand-rolled RFC 9421 canonicalizer (it's small and spec-tight;
org.tomitribe:http-signaturesis the wrong spec). Verifier test harness mirrors the TS one. SigningProvider+VerificationKeyResolverSPIs viaMETA-INF/services/. Signing takes explicitSigningContextrather than a singleAdcpUse; verification starts from inboundkidand only then maps to tenant/principal context. Receivers enforce purpose at JWKadcp_use.- Tenant-aware key selection at the signing boundary (D22). The API cannot model one global key per
adcp_use: multi-tenant operators need one JWKS endpoint with distinctkidvalues per publisher tenant under the sameadcp_use. The v0.2 signing surface freezesSigningContextinspecs/signing-context.md; v0.3 connects it toAccountStoretenant resolution. - In-process provider via JCA Ed25519 / ECDSA. No Bouncy Castle in core — JDK 21 has Ed25519 natively.
- AWS KMS provider via
software.amazon.awssdk:kms. Lazy-init. - GCP KMS provider via
com.google.cloud:google-cloud-kms. Lazy-init. - Optional
adcp-signing-bouncycastleartifact for FIPS environments. - Outbound webhook signing wired into
async-l3. - Pre-deploy KMS probe as a separate CLI command, not part of boot critical path.
Out of scope: Azure KMS (post-v1.0 on demand). Hardware HSM integration beyond JCA (post-v1.0).
Depends on: transport (signing wraps HTTP-level requests).
Milestone targets: v0.2 ships RFC 9421 + AWS+GCP KMS + webhook outbound signing, including typed webhook token echo, endpoint proof-of-control foundations, and externally managed webhook-signing metadata.
ID: multitenant | Owner: TBD | Size: 1.0 person-month
Scope:
AccountStoreSPI. Reference impls:InMemoryAccountStore(tests).JdbcAccountStoreagainst a Flyway/Liquibase-managed schema.- Optional
JpaAccountStoreif Spring Data JPA shops claim it.
RegistryClientSPI for agent-registry / brand-resolution lookup. Default impl points at the public AAO registry.- Multi-tenant principal resolution wired through the request handler.
- Per-tenant signing context export for D22: the resolved account/principal carries enough stable tenant identity for the L1 signer to choose the correct
kidwhen multiple publisher tenants share one operator JWKS endpoint. adagents.jsonresolver parity with TS/Python: authorization-type dispatch overproperty_ids,property_tags,inline_properties,publisher_properties,signal_ids, andsignal_tags; one-hop ads.txtMANAGERDOMAINfallback; revoked-domain semantics; permissive property resolution mode for operators that need diagnostics instead of hard fail.- Public placement catalog support from
adagents.json, including publisher-scopedplacement_refsand same-fileformat_option_idvalidation. - Wholesale product/signal cache model:
cache_scope,wholesale_feed_version,if_wholesale_feed_version, pricing-version tokens, and public-vs-account overlay invalidation. - Product-scoped signal targeting support: canonical
SignalRef,included_signals,signal_targeting_options,signal_targeting_rules, and package-leveltargeting_overlay.signal_targeting_groups. - Sandbox/live boundary enforcement at the
AccountStore(socomply_test_controllercalls returnCOMPLY_NOT_AVAILABLEon production accounts per spec). - Agent-card publication helper.
Out of scope: Caller-side credential presentation beyond what L0 already covers (folded into transport).
Depends on: codegen for principal / account types.
Milestone targets: v0.3 alpha ships full L2 plus wholesale feed cache-scope semantics.
ID: async-l3 | Owner: TBD | Size: 2.5 person-months
Scope:
IdempotencyStoreSPI with in-memory, JDBC, Redis (Lettuce) refs.IdempotencyConflictas a sealed type that structurally cannot carry a payload echo (read-oracle threat model fromL1/security.mdx#idempotency).- Byte-identical replay within TTL: store API takes and returns raw bytes alongside the typed response so replay can't accidentally re-serialize and drift.
TaskStoreSPI with generic artifact type — the compiler enforces that a task's terminal artifact carries the original tool's response shape, not a generic task envelope.WebhookEmittertwo-executor pattern (RFC §Async-task store and webhooks):scheduler: small platform-threadScheduledExecutorService(default size 1–2). Pure scheduling.dispatcher: separate executor that runs HTTP delivery. Default:Executors.newVirtualThreadPerTaskExecutor()(JDK 21 baseline).- Both executors injectable on
WebhookEmitter.builder().
- Async-result polling shape on the caller side.
- Error-recovery classification consumed from the spec's
error-code.jsonenumMetadata(PR #3738). SDK consumes; doesn't re-derive. IDEMPOTENCY_IN_FLIGHTwith transient recovery and claim-age-derivedretry_after; Java callers must retry without minting a new key.- Universal
idempotency_keyhandling for read and write tasks, including byte-identical replay andreplayedadvisory fields on cached read responses. - Durable account-level webhook subscriptions from
sync_accounts.accounts[].notification_configs[], withsubscriber_idstable replace/upsert semantics, endpoint proof-of-control, paused-config behavior, and typed webhook token echo. - Wholesale feed webhook parse/normalize/send helpers for
product.*,signal.*, andwholesale_feed.bulk_change; repair path is alwaysget_products/get_signals. - Advisory-success payload handling for stale cached upstreams (
STALE_RESPONSE) and SDK-generated projection warnings.
Out of scope: Persistence migrations beyond reference schemas (adopter responsibility).
Depends on: codegen, signing (webhook outbound), multitenant (sandbox boundary).
Milestone targets: Partial in v0.3 (idempotency + async tasks + cache-scope). Full webhook emitter and wholesale feed webhooks in v0.4.
ID: lifecycle | Owner: TBD | Size: 1.5 person-months (Java-side; coordination cost with TS/Python maintainers is separate)
Scope:
- Decide between RFC paths 1 and 2 (RFC §Lifecycle and transition validation). Recommendation in RFC: path 2 (lead the cross-SDK shared YAML lifecycle source). Decision depends on TS + Python maintainer commitment — see Decisions wanted.
- If path 2: author lifecycle YAMLs in the spec repo for the 7 resources (
MediaBuy,Creative,Account,SISession,CatalogItem,Proposal,Audience). Wire all three SDKs to consume them. - Transition validator API takes
(action, from, to), not(from, to)—NOT_CANCELLABLEprecedence overINVALID_STATErequires the action. - 3.1 action-discovery helper layer: parse
allowed_actions[]andavailable_actions[], modelupdate_media_buymutations as sealed request subtypes where possible, exposedecomposeUpdateMediaBuy()only as a compatibility fallback for legacy/non-sealed shapes, and surfaceACTION_NOT_ALLOWEDwith structured details before handler side effects. TransitionGuardSPI for adopter preconditions. Guards run after the spec edge check; can never relax a spec edge.- Guard narrowing protection: guards declare which edges they touch; conformance harness fails if a sandbox account's guards narrow any edge the storyboards exercise (RFC Open Question 7).
Out of scope: Adopter-side guard implementations (L4 concern).
Depends on: codegen for resource types, testing for harness integration.
Milestone targets: v0.4. Path 2 coordination starts immediately if WG signs off.
ID: comply | Owner: TBD | Size: 0.75 person-month
Scope:
seed_*/force_*/simulate_*controller surface matching@adcp/sdk's/conformanceand/compliance.- Sandbox-only enforcement wired at the
AccountStoreboundary (production →COMPLY_NOT_AVAILABLE). - 3.1 controller additions:
force_upstream_unavailable, typedsimulate_deliveryparams for reach/frequency/reach_window/viewability, webhook proof-of-control probes, stale-response exercises, and product-signal / canonical-format coverage. - Storyboard hint fix-plan format (
Diagnose / Locate / Fix / Verify) surfaced in adopter-facing test reports.
Depends on: multitenant (sandbox boundary), codegen.
Milestone targets: v0.4.
ID: testing | Owner: TBD | Size: 1.5 person-months
Scope:
adcp-testingartifact, JUnit 5 first-class.AdcpAgentExtension— JUnit 5 extension that boots an in-process agent (or wraps an adopter's agent) for storyboard runs.StoryboardRunner— Java port of TSrunStoryboard. Reads YAML storyboards from the protocol bundle, runs them against an agent under test, asserts wire conformance.- Mock-server forwarding adapter (critical — RFC §
comply_test_controller): storyboards certify against the shared reference mock-server, not an in-process Java mock. Without this, storyboards run against the SDK's own L4 stub instead of the spec-compliance oracle, and certification fails. - 3.1 runner output shape:
steps_not_selected,not_selected_by_reason, selected-but-skipped separation,storyboards_missing_tools/storyboards_not_applicable,RunnerNotice, and structured response-schema validation errors. - 3.1 storyboard input gating: consume
required_any_of_toolsas a storyboard-schema gate and surface failures viarequirement_unmetdetail. Keep account-discovery synthesized gating until the upstream bundle fully migrates. - Multi-bundle validator selection for compliance runs: a 3.1 runner can execute supplied 3.0 bundles with matching schema cache instead of assuming the installed SDK's primary pin.
- 3.1 storyboard primitives:
parallel_dispatch,cross_response_*checks, advisoryerrors[]assertions, stale-response placement, read-tool idempotency envelope fields, canonical-format projection, product-signal targeting, vendor-metric optimization, reach-window/viewability delivery reporting, billing finality, and action discovery. MockAgentfor callers under test (buyer-side mirror).Personasport of/testing/personas.- Signing test fixtures (port of
/signing/testing).
Out of scope: Test infrastructure beyond JUnit 5 (TestNG support is adopter's problem if they want it).
Depends on: codegen, transport. The mock-server forwarding adapter is the v0.1 release gate — without it, CI claims conformance it doesn't have.
Milestone targets: v0.1 ships the storyboard runner, mock-server forwarding adapter, 3.1 output splits/notices, and multi-bundle validator selection for compliance runs. Conformance test surface expands at each subsequent milestone as L1/L2/L3 land.
ID: spring | Owner: TBD | Size: 1.0 person-month
Scope:
adcp-spring-boot-starterauto-configures: handler, JacksonObjectMapper, signing provider, account store.- Micrometer
MeterRegistryintegration if on classpath. Metric names:adcp.tool.duration,adcp.signing.verify.failures, etc. - Actuator
AdcpHealthIndicatorif on classpath — reports signing-key reachability + account-store reachability. - Spring properties for tunables:
adcp.jackson.max-string-length, etc. - Spring Security integration as a documented recipe, not autoconfig (RFC §Server framework integration). Decision on
adcp-spring-boot-starter-securitydeferred to v0.3 feedback. jakartaonly, Spring Boot 3.x floor (per D7). Document the floor in the README so SB 2.7 shops don't burn an hour on the first import.
Out of scope: Quarkus / Micronaut / Servlet adapters (post-v1.0 on demand).
Depends on: transport, signing, multitenant, async-l3.
Milestone targets: Alpha in v0.3, polished by v1.0.
ID: kotlin | Owner: TBD | Size: 0.75 person-month
Scope:
adcp-kotlinextension artifact on top of the Java surface.- Coroutine
suspend funextension wrappers around the sync API. - DSL builders for request types.
- Nullability already correct because the Java surface is JSpecify-annotated.
Out of scope: Independent Kotlin SDK. Kotlin remains a thin layer on Java.
Depends on: Stable Java public surface (waits for v1.0 freeze of L0–L3 APIs).
Milestone targets: Co-released with v1.0.
ID: async-bridges | Owner: TBD | Size: 0.5 person-month
Scope:
adcp-reactor— wraps the sync surface inMono.fromCallable(...)on a bounded elastic scheduler.adcp-mutiny— Quarkus equivalent.- Both at GA, not fast-follow (RFC §Async model — WebFlux shops left to wrap the sync API will own that complexity forever).
Depends on: Stable Java public surface.
Milestone targets: v1.0.
ID: cli | Owner: TBD | Size: 0.5 person-month
Scope:
adcp-clirunnable jar. Commands:adcp <agent> [tool] [payload],adcp storyboard run,adcp grade.- Pre-deploy KMS probe command (from
signing). - Homebrew tap as a Java-leads add.
Out of scope: GraalVM native-image (post-v1.0 — RFC §Reference).
Depends on: transport, signing, testing.
Milestone targets: Alpha in v0.3, polished by v1.0.
ID: docs | Owner: TBD | Size: 1.0 person-month spread across milestones
Scope:
- JavaDoc on every public type. Generated and published.
- Migration guides for the four audiences (RFC §Migration path):
- Hand-rolled JVM agents.
- Python sidecar shops.
- Kotlin/JVM agents on Spring Boot.
- New JVM agents.
- Troubleshooting docs for the 8 RFC §Spec gotchas, especially the Jackson
StreamReadConstraintsfirst-hour bounce and Spring Boot 2.7NoSuchMethodError. - "Things we'd tell a new contributor" doc derived from the gotchas list.
Depends on: Each track contributes its slice of docs.
Milestone targets: Doc completeness gates v1.0 GA.
infra ──┬─→ codegen ──┬─→ transport ──┬─→ signing ──┬─→ async-l3 ──┬─→ spring
│ │ │ │ │
│ ├──→ testing ←──┘ │ │
│ │ │ │
│ ├─→ multitenant ──────────────┴─→ lifecycle │
│ │ │ │
│ │ └─→ comply │
│ │ │
│ └─→ (stable surface) ─→ kotlin │
│ ─→ async-bridges │
│ ─→ cli ────────────────┘
│
└─→ docs (cross-cutting)
Open an issue titled [track:<id>] <your name> claims <track name>. Include:
- Your prior JVM experience (records, sealed types, Jackson, Spring Boot, RFC 9421 / signing if relevant).
- Estimated availability per week.
- Which milestone you're committing to first.
- Whether you can attend the working-group sync.
A single contributor can claim multiple non-conflicting tracks (e.g. cli + docs). High-coupling tracks (async-l3 + lifecycle) benefit from a single owner pair.
In priority order, from the RFC. Items marked DONE are locked in Confirmed decisions; kept here as numbered references so RFC readers can map across.
- Funding / staffing. A contributed engineer at 50%+ for ~12 months, plus a named WG maintainer with merge rights, plus 2–3 design partners committed v0.1 → v0.4. Without all three, the RFC says decline and revisit at next major.
- Design partners. 2–3 JVM shops with letters of intent to ship on the SDK in 2026.
- WG vote on Java as the fourth officially supported language.
- Maintainer. Named owner with merge rights post-GA.
MCP Java SDK choice.DONE — D9.- Cross-SDK lifecycle YAML buy-in. TS + Python maintainers willing to consume a shared source. Decides
lifecyclepath 1 vs. path 2. DONE — D7.javaxvsjakartafloor.Reactor/Mutiny + Kotlin at GA vs. fast-follow.DONE — D13 (Reactor + Mutiny at GA) and D14 (Kotlin at GA, thin extension).
Additional decisions added post-RFC that remain open:
- MIT-licensed dependency position. D9 picked the MIT-licensed
io.modelcontextprotocol.sdk. License is compatible with Apache 2.0 downstream use, but the foundation may want an explicit position on accepting MIT deps in officially supported SDKs. - Funding model shape. RFC framing (contributed engineer at 50%+ for ~12 months + named maintainer + 2–3 design partners) is the right ask; whether it's pooled member funding, single-anchor-org contribution, or foundation grant is open.
- Design partner outreach. Anchor candidates by audience segment: one publisher running Spring Boot, one SSP, one broadcaster middleware team, and one EU ad-server vendor on Spring Boot 3.x / OAuth 2.1 / multi-tenant signing shape. Concrete company names belong in a private outreach tracker until they commit engineering time or an LOI; still need 2–3 committed design partners before scaling.
- WG vote timing. Recommendation: hold the vote at v0.1 alpha milestone (concrete working code) rather than now (abstract commitment).
- Hiring or sourcing the contributed engineer / design partners — that's a foundation-level conversation, not a planning artifact.
- An RFC for the cross-SDK lifecycle YAML — should be authored separately once TS + Python maintainers are at the table.
- A v1.x roadmap (Quarkus, Micronaut, Servlet, OkHttp, Apache HttpClient 5, Azure KMS, GraalVM, JPMS modules). All deferred to post-v1.0 demand.
| Stage | Status |
|---|---|
| RFC merged on adcontextprotocol/adcp | ✅ (PR #4279, 2026-05-13) |
| RFC imported to this repo | ✅ (docs/rfc/java-sdk-rfc.md) |
| Implementation plan drafted | ✅ (this doc) |
| Confirmed decisions | ✅ D1–D21 locked; D22 + D23 framing locked, shape/value details captured in this roadmap and linked specs. |
| Protocol parity check | 🟡 Behind — local Java pin is 3.0.11; latest upstream check is AdCP 3.1.0-beta.5, TS @adcp/sdk@8.1.0-beta.13, Python adcp@6.3.0b4 on 3.1.0-beta.4. |
| Funding / staffing confirmed | ⏳ Decision pending |
| Tracks claimed | 3 / 14 — infra (Track 1, #2), codegen (Track 2, #11), transport (Track 3, #17) |
| Pre-contributor harness | 🟡 In progress — Gradle skeleton, codegen MVP, SSRF skeleton, schema fetcher, mock-server CI gate, IPR workflow, commitlint, changesets, MCP prototype findings all landed. Foundation admin actions outstanding: IPR Bot install, DNS TXT for Sonatype, @MichielDean collaborator. |
| v0.1 alpha | Not Started |