@@ -12,7 +12,8 @@ yet exist.
1212## Status legend
1313
1414- ** Planned** — confirmed worth doing; deferred only for scope/sequencing.
15- - ** Decision needed** — a real fork with material trade-offs; pick a direction before writing code.
15+ - ** Decision needed** — a real fork with material trade-offs; pick a direction before writing
16+ code.
1617- ** Design** — forward-looking design for the (not-yet-started) code generator.
1718
1819---
@@ -22,22 +23,22 @@ yet exist.
2223The synchronous pipeline ships retry, bearer auth, and pagination; the asynchronous side does
2324not yet have equivalents. These bring the async path up to parity.
2425
25- ### Async retry step at the RETRY stage — [ #31 ] (Planned, large)
26+ ### Async retry step at the RETRY stage — #31 (Planned, large)
2627No ` AsyncHttpStep ` occupies ` Stage.RETRY ` , so async pipelines get no retry. Add a public
2728` AsyncRetryStep : AsyncHttpStep ` with ` final override val stage = Stage.RETRY ` mirroring the sync
2829` RetryStep ` , plus a concrete ` DefaultAsyncRetryStep ` that reuses ` HttpRetryOptions ` and a
2930` ScheduledExecutorService ` for non-blocking backoff (no thread parked during the delay). Reuse the
3031existing classification/` Retry-After ` logic. Should land after the retry-defaults reconciliation
3132(#38 ) so both async and sync share one backoff source of truth.
3233
33- ### Async bearer-auth with background token refresh — [ #32 ] (Planned, medium)
34+ ### Async bearer-auth with background token refresh — #32 (Planned, medium)
3435Add an async bearer-auth ` AsyncHttpStep ` and a non-blocking refresh path on the token provider
3536(` fetchAsync(...) : CompletableFuture<BearerToken> ` , defaulting to wrapping the blocking ` fetch ` ),
3637so a valid-but-near-expiry token is returned immediately while the refresh runs off-thread.
3738Coordinate with the bearer-token eviction work (#33 ) so the sync and async steps share eviction
3839and refresh semantics.
3940
40- ### Async pagination — [ #34 ] (Planned, large)
41+ ### Async pagination — #34 (Planned, large)
4142Both pagination surfaces are blocking today. After the pagination unification decision (#30 ),
4243add an async sibling that drives an iterative ` executeAsync ` re-arm, closing each page on
4344completion, with ` Flow ` /` Flux ` bridges living in the coroutines/reactor adapter modules (not in
@@ -49,38 +50,38 @@ completion, with `Flow`/`Flux` bridges living in the coroutines/reactor adapter
4950
5051Confirmed features deferred for scope. None is a defect; each adds a genuinely missing capability.
5152
52- ### Shared instrumentation emitter — [ #26 ] (Planned, medium)
53+ ### Shared instrumentation emitter — #26 (Planned, medium)
5354The sync and async instrumentation steps duplicate the emit/redact/preview/metrics logic, with a
5455standing extraction marker. The two copies have already diverged once. Extract the stateless logic
5556into an internal ` InstrumentationEmitters ` constructed from the values both steps need
5657(` HttpInstrumentationOptions ` , ` ClientLogger ` , ` Clock ` , the lazy metric instruments). Pure internal
5758refactor — high value for preventing future drift between the two steps.
5859
59- ### Per-call options channel on the request context — [ #27 ] (Planned, medium)
60+ ### Per-call options channel on the request context — #27 (Planned, medium)
6061` RequestContext ` carries no per-call override channel. Add an immutable ` RequestOptions `
6162(per-phase timeout overlay, response-validation toggle, ad-hoc credential override) with
6263` applyDefaults ` merge semantics, keeping the transport SPIs single-method. Depends on the
6364` Timeout ` value type from #41 .
6465
65- ### Per-phase ` Timeout ` value type — [ #41 ] (Planned, medium)
66+ ### Per-phase ` Timeout ` value type — #41 (Planned, medium)
6667No core timeout type exists; per-phase timeouts are configured ad hoc per transport. Add an
6768immutable ` Timeout ` (connect / read / write / request ` Duration ` s, read/write defaulting to
6869request) that adapters translate to native settings, kept distinct from the retry total-timeout.
6970Prerequisite for #27 .
7071
71- ### AutoCloseable SSE stream — [ #35 ] (Planned, medium)
72+ ### AutoCloseable SSE stream — #35 (Planned, medium)
7273The SSE surface is a bare ` Sequence ` , so a partially-consumed stream can leak the response. Add an
7374` SseStream : AutoCloseable, Iterable<ServerSentEvent> ` that owns the ` Response ` and closes it on
7475stream close or partial consumption — mirroring the close-on-partial-consume invariant pagination
7576already enforces.
7677
77- ### Multipart request body — [ #61 ] (Planned, large)
78+ ### Multipart request body — #61 (Planned, large)
7879No multipart support today. Add a public ` MultipartRequestBody : RequestBody ` (immutable + Builder)
7980with one shared frame-size function driving both ` writeTo ` and ` contentLength ` , file parts
8081streaming zero-copy via ` FileRequestBody ` and non-file parts encoded through the ` Serde ` SPI (no
8182Jackson in ` sdk-core ` ).
8283
83- ### Opt-in resource leak detector — [ #45 ] (Planned, medium)
84+ ### Opt-in resource leak detector — #45 (Planned, medium)
8485No leak detection exists. Add an internal, opt-in, log-only ` LeakDetector ` that WARNs when a
8586caller-owned closeable becomes phantom-reachable unclosed, using a reflectively-obtained
8687` java.lang.ref.Cleaner ` so Java-8 bytecode no-ops on JDK 8 and the whole thing is gated behind a
@@ -92,38 +93,39 @@ system property. Never auto-closes.
9293
9394These need a recorded decision (and sometimes a small spike) before any code is written.
9495
95- ### URL model: resolved ` java.net.URL ` vs deconstructed — [ #29 ] (Decision needed)
96+ ### URL model: resolved ` java.net.URL ` vs deconstructed — #29 (Decision needed)
9697** Recommended:** keep a single opaque URL field on ` Request ` (do not explode into
9798scheme/host/port/segments/query on the immutable model), but migrate the stored type from
9899` java.net.URL ` to ` java.net.URI ` — ` URI ` is parse-only, DNS-free, and already what the JDK
99100transport needs. Preserve the existing textual, DNS-free equality. Record the decision in
100101` docs/architecture.md ` . Gates #28 and the typed-page generation in #56 .
101102
102- ### First-class ` QueryParams ` multimap — [ #28 ] (Planned, medium; gated on #29 )
103+ ### First-class ` QueryParams ` multimap — #28 (Planned, medium; gated on #29 )
103104` QueryParam ` is a ` TODO() ` stub and ` RequestRebuilder ` does query-string surgery by splitting on
104105` & ` with single-value semantics. Add a public ` QueryParams ` multimap modeled on ` Headers `
105106(insertion-ordered, multi-value, explicit encoding rules). Lands after the URL-model decision so it
106107projects into the chosen type.
107108
108- ### Unify the two pagination stacks — [ #30 ] (Decision needed, large)
109+ ### Unify the two pagination stacks — #30 (Decision needed, large)
109110Two parallel pagination surfaces exist and are both public API: ` http.paging `
110111(` PagedIterable ` /` PagedResponse ` , BYO fetcher) and ` pagination ` (` Paginator ` /` Page ` + strategies,
111112self-driving). ** Recommended:** make the strategy-driven ` Paginator ` + ` Page ` the canonical
112113surface (it owns the client, matches peer SDKs, and is the model the typed-page generation in #56
113114builds on) and deprecate the other. A public-API redesign — decide before coding. Gates #34 .
114115
115- ### Deep-array value-equality utility — [ #49 ] (Decision needed, small)
116+ ### Deep-array value-equality utility — #49 (Decision needed, small)
116117A ` contentEquals ` /` contentHashCode ` helper has no consumer in the current tree; it is tied to the
117118future generated DTOs (with ` ByteArray ` fields). ** Recommended:** defer until codegen needs it, to
118119avoid adding public surface that immediately churns the API snapshot and the coverage floor with no
119120caller. Fold into the codegen runtime when that lands.
120121
121- ### VirtualThreads close-event log level — [ #10 ] (Decision needed, trivial)
122- The ` executor.closed ` event is emitted at DEBUG (not INFO). ** Recommended:** keep DEBUG (it matches
123- every other async-adapter event and keeps clean-shutdown noise off INFO) and close the issue,
124- optionally noting the intent in the ` close() ` KDoc. No code change beyond the optional doc line.
122+ ### VirtualThreads close-event log level — #10 (Decision needed, trivial)
123+ The ` executor.closed ` event is emitted at the SDK's VERBOSE level (which maps to SLF4J DEBUG), not
124+ INFO. ** Recommended:** keep VERBOSE (it matches every other async-adapter event and keeps
125+ clean-shutdown noise off INFO) and close the issue, optionally noting the intent in the ` close() `
126+ KDoc. No code change beyond the optional doc line.
125127
126- ### Release automation — [ #75 ] (Decision needed, gated on CI)
128+ ### Release automation — #75 (Decision needed, gated on CI)
127129Versioning, changelog, and publishing are manual, and the version string is duplicated across ~ 10
128130build scripts. ** Recommended (once CI lands):** record a decision on adopting release-please; if
129131adopted, collapse the duplicated version to a single anchor and add a release workflow + Sonatype
@@ -139,67 +141,70 @@ clients over this toolkit. None is a defect, and most cannot become code until t
139141its keystone runtime type (#50 ) exist. They are captured here (and several warrant short design-doc
140142sections now) so the effort can start coherently.
141143
142- ### Keystone: dependency-free four-state JSON field model — [ #50 ] (Design, large)
144+ ### Keystone: dependency-free four-state JSON field model — #50 (Design, large)
143145The foundation the rest of the model generation builds on: a dep-free sealed ` JsonField<T> `
144146(` Known ` / ` Missing ` / ` Null ` / ` Raw ` ) plus a ` RawJson ` tree in ` sdk-core ` , with all
145- Jackson↔` RawJson ` conversion confined to the ` sdk-serde-jackson ` adapter — mirroring how ` Tristate `
146- is split today. Almost every other codegen item depends on this. ** Land a design doc first **
147- (` docs/codegen-json-field.md ` ), then the runtime type, then the generator template.
147+ Jackson↔` RawJson ` conversion confined to the ` sdk-serde-jackson ` adapter — mirroring how
148+ ` Tristate ` is split today. Almost every other codegen item depends on this. ** Land a design doc
149+ first ** (` docs/codegen-json-field.md ` ), then the runtime type, then the generator template.
148150
149151### Generated model shape (all depend on #50 )
150- - ** Thin models over a hand-written runtime — [ #51 ] ** (Design): generated models stay <100 lines
152+ - ** Thin models over a hand-written runtime — #51 ** (Design): generated models stay <100 lines
151153 (fields + accessors) while the runtime owns the forward-compat machinery.
152- - ** ` additionalProperties ` pass-through — [ #52 ] ** (Design): capture unknown fields into an immutable
154+ - ** ` additionalProperties ` pass-through — #52 ** (Design): capture unknown fields into an immutable
153155 ` Map<String, RawJson> ` so read-modify-write round-trips don't drop server-added fields.
154- - ** Unions as private-ctor + per-variant accessors + visitor — [ #53 ] ** (Design): Java-8-safe
156+ - ** Unions as private-ctor + per-variant accessors + visitor — #53 ** (Design): Java-8-safe
155157 ` oneOf ` emission with a retained raw node and an ` unknown(raw) ` fallback.
156- - ** Forward-compatible enums — [ #54 ] ** (Design): open value + known/value pair so deserialization
158+ - ** Forward-compatible enums — #54 ** (Design): open value + known/value pair so deserialization
157159 never throws on an unrecognized server value.
158- - ** Discriminator/const fields as defaulted raw values with dual accessors — [ #65 ] ** (Design):
160+ - ** Discriminator/const fields as defaulted raw values with dual accessors — #65 ** (Design):
159161 fold into the #50 design effort.
160- - ** Optional ` validate() ` /` isValid() ` /` validity() ` triad — [ #64 ] ** (Design): opt-in, memoized,
162+ - ** Optional ` validate() ` /` isValid() ` /` validity() ` triad — #64 ** (Design): opt-in, memoized,
161163 off the deserialize path; used only as last-resort union disambiguation behind a discriminator.
162164
163165### Generated service/client shape
164- - ** Two-tier raw/cooked service methods — [ #55 ] ** (Design): a "cooked" method returning the parsed
166+ - ** Two-tier raw/cooked service methods — #55 ** (Design): a "cooked" method returning the parsed
165167 body and a "raw" method returning a lazy ` ParsedResponse<T> ` . Builds on the ` ResponseHandler `
166- seam (#36 , now in review ) and the operation-params SPI (#57 ).
167- - ** Minimal ` OperationParams ` SPI — [ #57 ] ** (Design): projects an operation's inputs into
168+ seam (#36 ) and the operation-params SPI (#57 ).
169+ - ** Minimal ` OperationParams ` SPI — #57 ** (Design): projects an operation's inputs into
168170 headers/query/path/body and feeds the context chain. Gated on the ` QueryParams ` multimap (#28 ).
169- - ** Curated operation overload set — [ #58 ] ** (Design): one canonical method per operation plus a
171+ - ** Curated operation overload set — #58 ** (Design): one canonical method per operation plus a
170172 small curated overload set leaning on Kotlin default arguments, rather than the full overload
171173 cross-product.
172- - ** Lazy sub-service accessor tree — [ #59 ] ** (Design): ` by lazy ` sub-service accessors on a
174+ - ** Lazy sub-service accessor tree — #59 ** (Design): ` by lazy ` sub-service accessors on a
173175 generated root client, reusing a nested raw-response impl.
174- - ** ` withOptions(Consumer<Builder>) ` returning a new immutable client — [ #60 ] ** (Design): gated on
176+ - ** ` withOptions(Consumer<Builder>) ` returning a new immutable client — #60 ** (Design): gated on
175177 first deciding whether to introduce a single cloneable client-config with ` toBuilder() ` .
176- - ** Typed page classes that rebuild typed params — [ #56 ] ** (Design, xlarge): ` nextPage() `
178+ - ** Typed page classes that rebuild typed params — #56 ** (Design, xlarge): ` nextPage() `
177179 re-invokes the operation with a typed param object, not a spliced URL string. Gated on #57 , #28 ,
178180 and the pagination unification (#30 ).
179- - ** Per-endpoint SSE adapter — [ #62 ] ** (Design): maps an ` SseStream ` to a lazily-decoded
181+ - ** Per-endpoint SSE adapter — #62 ** (Design): maps an ` SseStream ` to a lazily-decoded
180182 ` Iterable<TModel> ` . Gated on #35 and #36 .
181- - ** Per-operation auth descriptors with a precedence ladder — [ #63 ] ** (Design): the generator emits
183+ - ** Per-operation auth descriptors with a precedence ladder — #63 ** (Design): the generator emits
182184 an ` AuthMetadata ` per operation; ` sdk-core ` 's auth step consumes it scheme-agnostically. The
183185 scheme-agnostic primitives partly exist already.
184186
185187### Generator plumbing & outputs
186- - ** Strict structured-output JSON-schema encoding rules — [ #66 ] ** (Design, small): capture the
188+ - ** Strict structured-output JSON-schema encoding rules — #66 ** (Design, small): capture the
187189 encoding contract (all-required + ` additionalProperties:false ` + optional-as-nullable-union) as a
188190 design-doc section now; it is adapter-only, never ` sdk-core ` .
189- - ** Reusable fail-soft recursive validator skeleton — [ #67 ] ** (Design, small): a small generic
191+ - ** Reusable fail-soft recursive validator skeleton — #67 ** (Design, small): a small generic
190192 recursion-guarded, path-prefixed validator idiom for the generator's own IR; build in codegen
191193 week 1–2, once the IR exists.
192- - ** Provenance file stamped into generated SDKs — [ #68 ] ** (Design, small): generator version +
194+ - ** Provenance file stamped into generated SDKs — #68 ** (Design, small): generator version +
193195 input-contract hash, emitted into generated output only, never into the hand-written toolkit.
194- - ** Spring Boot starter per generated API — [ #69 ] ** (Design, medium): an optional sibling
196+ - ** Spring Boot starter per generated API — #69 ** (Design, medium): an optional sibling
195197 ` <api>-spring-boot-starter ` with ` @ConfigurationProperties ` , a customizer ` fun interface ` , and an
196198 ` @AutoConfiguration ` assembling {IoProvider + transport + HttpPipeline}, keeping Spring out of
197199 ` sdk-core ` and the generated client.
198200
199201### Suggested codegen sequencing
200202
201- 1 . Design docs: #50 (keystone), #66 , #67 , #68 — these can be written now, before any generator code.
203+ 1 . Design docs: #50 (keystone), #66 , #67 , #68 — these can be written now, before any generator
204+ code.
2022052 . Land #50 's runtime type in ` sdk-core ` ; decide #29 (URL model) and #28 (` QueryParams ` ).
203- 3 . Stand up the generator IR + the fail-soft validator (#67 ), then model emission (#51 –#54 , #64 , #65 ).
204- 4 . Service/client emission (#55 , #57 , #58 , #59 , #60 ), then pagination/SSE/auth generation (#56 , #62 , #63 ).
206+ 3 . Stand up the generator IR + the fail-soft validator (#67 ), then model emission
207+ (#51 –#54 , #64 , #65 ).
208+ 4 . Service/client emission (#55 , #57 , #58 , #59 , #60 ), then pagination/SSE/auth generation
209+ (#56 , #62 , #63 ).
2052105 . Packaging outputs (#68 provenance, #69 Spring starter).
0 commit comments