Skip to content

Commit 63e429b

Browse files
committed
docs: refine roadmap follow-up specs
1 parent 787b080 commit 63e429b

3 files changed

Lines changed: 36 additions & 12 deletions

File tree

ROADMAP.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ Read from the TS and Python SDK changelogs plus the AdCP 3.1 beta release notes.
6161
- **Open error-code decoding.** As SDK forward-compatibility practice, receivers should not fail closed on unknown `error.code`; classify from `recovery` and default conservatively when absent. Java generated enums need an unknown-value strategy, not a hard enum parse failure. → [`codegen`](#track-2--l0-types--codegen) + [`transport`](#track-3--l0-transport-mcp--a2a).
6262
- **Advisory `errors[]` on success payloads.** `STALE_RESPONSE`, canonical-format projection warnings, pixel-tracker downgrade/upgrade warnings, and similar advisories ride in payload `errors[]` while transport/task success remains success. Java callers and validators must not promote advisory payload errors to thrown failures. → [`transport`](#track-3--l0-transport-mcp--a2a) + [`testing`](#track-9--testing--conformance).
6363
- **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`](#track-2--l0-types--codegen) + [`transport`](#track-3--l0-transport-mcp--a2a) + [`async-l3`](#track-6--l3-idempotency-async-tasks-webhooks).
64-
- **Webhook token round-trip and endpoint proof-of-control.** `McpWebhookPayload.token` is typed; durable account-level webhook configs require proof-of-control semantics and stable `subscriber_id` replace/upsert behavior. → [`signing`](#track-4--l1-signing) + [`async-l3`](#track-6--l3-idempotency-async-tasks-webhooks) + [`multitenant`](#track-5--l2-account-store-registry-multi-tenant).
64+
- **Webhook token round-trip.** `McpWebhookPayload.token` is typed and must echo through webhook dispatch/receipt paths. → [`signing`](#track-4--l1-signing) + [`async-l3`](#track-6--l3-idempotency-async-tasks-webhooks).
65+
- **Endpoint proof-of-control.** Durable account-level webhook configs require proof-of-control semantics and stable `subscriber_id` replace/upsert behavior. → [`async-l3`](#track-6--l3-idempotency-async-tasks-webhooks) + [`multitenant`](#track-5--l2-account-store-registry-multi-tenant).
6566

6667
**3.1 buying, catalog, and signal surface**
6768

@@ -71,11 +72,13 @@ Read from the TS and Python SDK changelogs plus the AdCP 3.1 beta release notes.
7172
- **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.2 `capability_ids` write path was removed before beta.5; Java should model the beta.5 `format_option_*` names from the start. → [`codegen`](#track-2--l0-types--codegen) + [`transport`](#track-3--l0-transport-mcp--a2a).
7273
- **Public placement catalogs.** `adagents.json` can publish placement catalogs and publisher-scoped `placement_refs`; seller-private routing stays out of public placement schemas. → [`multitenant`](#track-5--l2-account-store-registry-multi-tenant).
7374
- **Vendor-attested measurement.** `vendor_metric` optimization goals, per-product `vendor_metric_optimization`, reporting-coherence preconditions, and compliance coverage. → [`codegen`](#track-2--l0-types--codegen) + [`testing`](#track-9--testing--conformance).
74-
- **Delivery and billing finality.** `reach_window`, `viewability.viewed_seconds`, windowed pull recovery, row-level delivery finality, `report_usage` finality, and `BILLING_OUT_OF_BAND`. → [`codegen`](#track-2--l0-types--codegen) + [`testing`](#track-9--testing--conformance).
75+
- **Delivery and billing finality.** `reach_window`, `viewability.viewed_seconds`, windowed pull recovery, row-level delivery finality, `report_usage` finality, and `BILLING_OUT_OF_BAND` as an error-code surface. → [`codegen`](#track-2--l0-types--codegen) + [`testing`](#track-9--testing--conformance).
7576
- **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 around `update_media_buy` mutations instead of forcing every adopter to re-parse action intent. → [`lifecycle`](#track-7--l3-lifecycle--transitions) + [`transport`](#track-3--l0-transport-mcp--a2a).
7677

7778
**Cross-SDK helper surface to match**
7879

80+
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.
81+
7982
- **TS `@adcp/sdk@8.1` helper 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 `*Payload` aliases, and 3.1 compliance/cache selection in the runner. Java equivalents belong in the main `adcp`, `adcp-server`, and `adcp-testing` artifacts rather than new artifacts.
8083
- **Python `adcp@6.x` helper 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.
8184

@@ -196,7 +199,7 @@ Each track entry has:
196199

197200
| Milestone | Target | Release gate |
198201
|---|---|---|
199-
| v0.1 alpha | M+3 | L0 surface compiles against the D23 target bundle (`3.1.0-beta.5`, or 3.1 GA if cut before the codegen PR), wire-version negotiation works for stable and prerelease tokens, 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). |
202+
| v0.1 alpha | M+3 | D23 target bundle compiles (`3.1.0-beta.5`, or 3.1 GA if cut before the codegen PR).<br>Wire-version negotiation works for stable and prerelease tokens.<br>Multi-bundle validators can serve 3.0 and 3.1 traffic.<br>SSRF/auth discovery baseline lands.<br>Storyboards green against reference mock-server in CI.<br>Local Gradle artifacts only (per D6 — first Maven Central publish at v0.3). |
200203
| v0.2 alpha | M+4 | L1: RFC 9421 signing/verification, AWS+GCP KMS providers (lazy-init, tenant-aware per-`adcp_use` key selector per D22), webhook signing, typed webhook token / proof-of-control foundations |
201204
| 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). |
202205
| v0.4 beta | M+9 | Full L3: transition validators, webhook emission, wholesale feed webhooks, `comply_test_controller`, A2A transport, 3.1 compliance bundle parity |
@@ -231,7 +234,7 @@ The RFC's M+12 target is the realistic line. Pre-committing M+9 and slipping is
231234

232235
### Track 2 — L0 types & codegen
233236

234-
**ID:** `codegen` | **Owner:** TBD | **Size:** 2.0 person-months
237+
**ID:** `codegen` | **Owner:** TBD | **Size:** 2.5 person-months
235238

236239
**Scope:**
237240

@@ -259,7 +262,7 @@ The RFC's M+12 target is the realistic line. Pre-committing M+9 and slipping is
259262

260263
### Track 3 — L0 transport: MCP + A2A
261264

262-
**ID:** `transport` | **Owner:** @MichielDean (#17) | **Size:** 1.5 person-months
265+
**ID:** `transport` | **Owner:** @MichielDean (#17) | **Size:** 2.0 person-months
263266

264267
**Scope:**
265268

@@ -289,7 +292,7 @@ The RFC's M+12 target is the realistic line. Pre-committing M+9 and slipping is
289292
**Scope:**
290293

291294
- Hand-rolled RFC 9421 canonicalizer (it's small and spec-tight; `org.tomitribe:http-signatures` is the wrong spec). Verifier test harness mirrors the TS one.
292-
- `SigningProvider` SPI via `META-INF/services/`. API shape takes explicit `SigningContext` rather than a single `AdcpUse`; receivers enforce purpose at JWK `adcp_use`.
295+
- `SigningProvider` + `VerificationKeyResolver` SPIs via `META-INF/services/`. Signing takes explicit `SigningContext` rather than a single `AdcpUse`; verification starts from inbound `kid` and only then maps to tenant/principal context. Receivers enforce purpose at JWK `adcp_use`.
293296
- 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 distinct `kid` values per publisher tenant under the same `adcp_use`. The v0.2 signing surface freezes `SigningContext` in [`specs/signing-context.md`](specs/signing-context.md); v0.3 connects it to `AccountStore` tenant resolution.
294297
- In-process provider via JCA Ed25519 / ECDSA. **No Bouncy Castle in core** — JDK 21 has Ed25519 natively.
295298
- AWS KMS provider via `software.amazon.awssdk:kms`. Lazy-init.
@@ -336,7 +339,7 @@ The RFC's M+12 target is the realistic line. Pre-committing M+9 and slipping is
336339

337340
### Track 6 — L3 idempotency, async tasks, webhooks
338341

339-
**ID:** `async-l3` | **Owner:** TBD | **Size:** 2.0 person-months
342+
**ID:** `async-l3` | **Owner:** TBD | **Size:** 2.5 person-months
340343

341344
**Scope:**
342345

@@ -567,7 +570,7 @@ Additional decisions added post-RFC that remain open:
567570

568571
9. **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.
569572
10. **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.
570-
11. **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. ADvendio is a concrete reviewer candidate in that last segment, but has not committed engineering time or an LOI; still need 2–3 committed design partners before scaling.
573+
11. **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.
571574
12. **WG vote timing.** Recommendation: hold the vote at v0.1 alpha milestone (concrete working code) rather than now (abstract commitment).
572575

573576
## What's not in this plan (yet)

specs/codegen-open-enums.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,18 @@ public sealed interface ErrorCode permits ErrorCode.Known, ErrorCode.Unknown {
3030

3131
The generated JSON adapter maps recognized wire values to `Known` and unrecognized wire values to `Unknown`. Serialization preserves `rawValue()` exactly.
3232

33+
## Jackson binding
34+
35+
Open enum wrappers do not use Jackson `@JsonTypeInfo`; they serialize as the flat protocol string. The generator emits a custom `JsonDeserializer<T>` for each open vocabulary that maps the incoming string to `Known` or `Unknown`, plus a matching `JsonSerializer<T>` that writes `rawValue()`.
36+
37+
This is separate from polymorphic envelope handling in Track 2. Envelope types may use discriminator-based Jackson handling; open vocabularies must not, because the wire value is a scalar string.
38+
3339
## Generator rules
3440

3541
- Open vocabularies generate a sealed wrapper with `Known` and `Unknown`.
3642
- The nested known-value type may be a Java enum when the known value set is useful for switch exhaustiveness.
3743
- Unknown raw values are never rewritten, lowercased, uppercased, or mapped to a generic `UNKNOWN` sentinel that loses the original string.
44+
- Each open vocabulary emits Jackson serializer/deserializer bindings that preserve the flat scalar wire shape.
3845
- Closed vocabularies may generate plain Java enums.
3946
- The schema post-processor owns the open/closed classification; contributors must not infer it from value count.
4047

specs/signing-context.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,25 @@ public record SigningContext(
2424
}
2525
```
2626

27-
The public signing SPI takes `SigningContext`:
27+
The public signing SPI takes `SigningContext` on the outbound signing path:
2828

2929
```java
3030
public interface SigningProvider {
3131
Signature sign(SigningContext context, SigningInput input);
32-
VerificationResult verify(SigningContext context, SignedInput input);
32+
}
33+
```
34+
35+
Inbound verification starts from the signed request, especially the `kid` header. The resolver maps the inbound key id to a verification key and any resolved tenant/principal metadata; the verifier then checks the signature and `adcp_use` purpose.
36+
37+
```java
38+
public interface VerificationKeyResolver {
39+
VerificationKey resolve(VerificationInput input);
40+
}
41+
42+
public record VerificationInput(
43+
AdcpUse expectedUse,
44+
String kid,
45+
SignedInput input) {
3346
}
3447
```
3548

@@ -41,8 +54,9 @@ There is no release API shaped as `SigningProvider.forUse(AdcpUse)`. If a protot
4154
- `tenant` is nullable for single-tenant deployments and caller-side signing where no publisher account has been resolved yet.
4255
- `principal` is nullable and carries the resolved account/principal when available.
4356
- Providers may use `tenant`, `principal`, or both to select `kid`; they must not ignore `use`.
44-
- Verification still enforces the key purpose at JWK `adcp_use`; tenant context only narrows which key is expected or acceptable.
57+
- Verification starts from inbound `kid`; tenant context is derived after key lookup and never assumed before signature verification.
58+
- Verification still enforces the key purpose at JWK `adcp_use`.
4559

4660
## Milestone contract
4761

48-
v0.2 freezes the `SigningContext` type and SPI method signatures. v0.3 wires `AccountStore` / `adagents.json` principal resolution into the context passed to webhook and signed-request providers.
62+
v0.2 freezes the `SigningContext`, `SigningProvider`, and `VerificationKeyResolver` shapes. v0.3 wires `AccountStore` / `adagents.json` principal resolution into the context passed to webhook and signed-request providers.

0 commit comments

Comments
 (0)