Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 222 additions & 0 deletions .sdk-sync/2.0.0-ffb34b7.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
# sdk-auto-sync job log

| | |
|---|---|
| API release | 2.0.0 (commit `ffb34b7`) |
| SDK | sdk-java |
| Branch | `sdk-sync/2.0.0-ffb34b7` |
| API surface | `ae2b047c8f6f` → `c2a31e277345` |
| Date | 2026-10-01 12:57:05Z |
| Outcome | **done** |

## Changes in the API surface

~ GET /v1/options/chain/{underlying}/ ?dte: string (was integer)

## 1. test plan

Coder (12 turns, US$ 0.65) — test plan, 14 case(s) (wire 4, regression 1, validation 8, parity 1):

> Adds three new additive ExpirationFilter factories (dteList, dteRange, dteComparison) plus a DteOperator enum, leaving the existing dte(int)/Dte record untouched (per the repo's own binary/source-compatibility rule against retyping an existing parameter). Tests cover one wire case per enumerated form (single, comma-list, closed range, comparison), a regression case proving the legacy dte(int) factory and Dte record shape are unchanged, 8 validation cases for the new "non-negative, at most 36500" bound across the three new entry points plus the legacy factory's new upper bound, and one sync/async parity case for the new range form. All cases land in OptionsResourceTest.java, matching where ExpirationFilter is already tested.

| # | category | test | API form | input | expected |
|---|---|---|---|---|---|
| 1 | wire | `dte_single_value_renders_bare_number` | A single value | options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(30)).build()) | captured URI equals .../options/chain/AAPL/?dte=30 |
| 2 | wire | `dte_comma_separated_list_renders_joined_values` | A comma-separated list (15,30,45) | options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dteList(15, 30, 45)).build()) | URL-decoded captured URI contains dte=15,30,45 |
| 3 | wire | `dte_closed_range_renders_as_dashed` | A closed range (0-45) | options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dteRange(0, 45)).build()) | captured URI equals .../options/chain/AAPL/?dte=0-45 |
| 4 | wire | `dte_comparison_operator_renders_with_operator_prefix` | A comparison operator (>=30, <45) | options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dteComparison(ExpirationFilter.DteOperator.GTE, 30)).build()) | captured URI contains dte=%3E%3D30 |
| 5 | regression | `legacy_dte_int_factory_and_record_shape_unchanged` | A single value (pre-existing integer form) | ExpirationFilter.Dte d = (ExpirationFilter.Dte) ExpirationFilter.dte(45); d.days(); and ExpirationFilter.dte(-1) | d.days() == 45; ExpirationFilter.dte(-1) still throws IllegalArgumentException "dte must be non-negative" |
| 6 | validation | `dte_single_value_rejects_above_36500` | Values are whole numbers of days, at most 36500 | ExpirationFilter.dte(36501) | IllegalArgumentException "dte must be at most 36500" |
| 7 | validation | `dte_list_rejects_negative_element` | A comma-separated list (15,30,45) | ExpirationFilter.dteList(15, -1, 45) | IllegalArgumentException "dte must be non-negative" |
| 8 | validation | `dte_list_rejects_element_above_36500` | Values are whole numbers of days, at most 36500 | ExpirationFilter.dteList(15, 36501, 45) | IllegalArgumentException "dte must be at most 36500" |
| 9 | validation | `dte_range_rejects_min_greater_than_max` | A closed range (0-45) | ExpirationFilter.dteRange(45, 0) | IllegalArgumentException "min must be <= max" |
| 10 | validation | `dte_range_rejects_negative_min` | A closed range (0-45) | ExpirationFilter.dteRange(-1, 45) | IllegalArgumentException "dte must be non-negative" |
| 11 | validation | `dte_range_rejects_max_above_36500` | Values are whole numbers of days, at most 36500 | ExpirationFilter.dteRange(0, 36501) | IllegalArgumentException "dte must be at most 36500" |
| 12 | validation | `dte_comparison_rejects_negative_value` | A comparison operator (>=30, <45) | ExpirationFilter.dteComparison(ExpirationFilter.DteOperator.GTE, -1) | IllegalArgumentException "dte must be non-negative" |
| 13 | validation | `dte_comparison_rejects_value_above_36500` | Values are whole numbers of days, at most 36500 | ExpirationFilter.dteComparison(ExpirationFilter.DteOperator.LT, 36501) | IllegalArgumentException "dte must be at most 36500" |
| 14 | parity | `dte_range_sync_mirrors_async_wire_request` | A closed range (0-45) | options.chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dteRange(0, 45)).build()) vs chainAsync with same filter | both captured URIs equal .../options/chain/AAPL/?dte=0-45 |

Plan reviewer (11 turns, US$ 0.26): approved

> The plan covers all four enumerated dte forms (single, list, range, comparison) with wire cases, a regression case for the legacy integer factory, validation for non-negative/36500-max/range-order across all variants, and a sync/async parity case — matching the sealed ExpirationFilter/StrikeFilter patterns already in the codebase. No invented forms, no missing forms, and placement in OptionsResourceTest.java is correct; the only issue is a naming-convention mismatch.

- [minor] src/test/java/com/marketdata/sdk/OptionsResourceTest.java — All 14 planned test names use snake_case (e.g. dte_single_value_renders_bare_number), but every existing test method in this file uses camelCase (chainExpirationFilterDteTranslatesToDteParam, expirationFilterDteRejectsNegative). Rename to match the file's convention.

## 2. tests

Coder (4 turns, US$ 0.57):

> Build fails as expected — missing symbols (`dteList`, `dteRange`, `dteComparison`, `DteOperator`) confirm the tests target the not-yet-implemented API surface. There's also one benign redundant-cast warning I'll leave, since it's valid for the regression test's intent (documenting the record cast explicitly).
>
> Added 14 tests to `OptionsResourceTest.java` per the reviewed plan: 4 wire cases (single value, comma-list, closed range, comparison operator), 1 regression case (legacy `dte(int)`/`Dte` shape and negative-rejection unchanged), 8 validation cases (non-negative/≤36500 bounds across the single, list, range, and comparison entry points, plus range min>max), and 1 sync/async parity case for the range form. `gradle test --no-daemon --console=plain` fails at `compileTestJava` on the missing `dteList`/`dteRange`/`dteComparison`/`DteOperator` symbols, as expected since no production code was touched.

Gates:

- tests (expected to fail), attempt 1: failed — Execution failed for task ':compileTestJava' (registered by plugin class 'org.gradle.api.plugins.JavaBasePlugin').

## 3. implement

Coder (20 turns, US$ 0.65):

> Only the expected files changed. Not committing, per instructions.
>
> Added three additive `ExpirationFilter` factories — `dteList`, `dteRange`, `dteComparison` — plus a `DteOperator` enum, implementing the API's new comma-list/range/comparison `dte` syntax while leaving the existing `dte(int)`/`Dte` record untouched for binary/source compatibility (per the repo's own ADR-008/REVIEW.md convention). Added shared `MAX_DTE_DAYS` (36500) bound validation across all four entry points. Wired the new variants into `OptionsResource.applyExpirationFilter` for wire encoding. Fixed one redundant-cast and two Spotless line-wrap issues in the tests I wrote in phase 1 (no assertions weakened). `gradle test` and `gradle build` both pass.

Gates:

- tests, attempt 1: passed
- build, attempt 1: passed

## 4. review 1

Reviewer (6 turns, US$ 0.39): approved

> The implementation correctly adds dteList/dteRange/dteComparison variants to ExpirationFilter, wires them into OptionsResource's query building with the right wire syntax, and preserves the legacy dte(int) factory and Dte record shape. All 14 specified test cases are present with matching inputs, expected messages, and assertions, and none appear weakened. No blocker or major issues found in the three in-scope files.

- [minor] docs/options/chain.md — Docs only show ExpirationFilter.dte(int) for the dte filter; the new dteList/dteRange/dteComparison factories added for the dte string-syntax expansion aren't documented. Not in the author's changed-file set for this review but should be updated by a maintainer.

Security (2 turns, US$ 0.27): approved

> This change adds three new `dte` wire syntaxes (list, range, comparison) to `ExpirationFilter`, mirroring the existing `StrikeFilter` pattern. All new values are validated integers (0–36500) or enum-controlled operator prefixes, so there is no injection surface — the query builder (unchanged, pre-existing) URL-encodes the resulting strings, as confirmed by the `%3E%3D` test assertions. No new dependencies, secrets, logging, or file I/O are introduced, and the test file matches the committed test plan exactly with no weakened cases.

- no findings

## 5. assess

Analyst (4 turns, US$ 0.09) — public surface:

> The `ExpirationFilter` sealed interface gains four variants to support the API's new string-based `dte` parameter syntax (comma-separated list, range, and comparison operators). The existing `dte(int)` method's validation is stricter (now rejects values above 36500 days); new consumers can use `dteList()`, `dteRange()`, and `dteComparison()` factories alongside it.

- added: `ExpirationFilter.MAX_DTE_DAYS`
- added: `ExpirationFilter.dteList(int...)`
- added: `ExpirationFilter.DteList`
- added: `ExpirationFilter.dteRange(int, int)`
- added: `ExpirationFilter.DteRange`
- added: `ExpirationFilter.dteComparison(DteOperator, int)`
- added: `ExpirationFilter.DteComparison`
- added: `ExpirationFilter.DteOperator`
- added: `ExpirationFilter.DteOperator.GT`
- added: `ExpirationFilter.DteOperator.GTE`
- added: `ExpirationFilter.DteOperator.LT`
- added: `ExpirationFilter.DteOperator.LTE`
- added: `ExpirationFilter.DteOperator.wireValue()`
- changed: `ExpirationFilter.dte(int)` — Now validates upper bound (36500 days); previously accepted any non-negative value.

Computed by the pipeline: **major**, 1.0.0 → 2.0.0; changelog entry written

## 6. changelog

Coder (4 turns, US$ 0.06):

> Added two changelog sections under `[Unreleased]`: **Added** documenting the new `dteList`, `dteRange`, `dteComparison` factories and `DteOperator` enum for API 2.0.0, and **Changed** noting the breaking upper-bound validation on `dte(int)` with migration guidance.

Gates:

- build, attempt 1: passed

## Totals

- Version: major · 1.0.0 → 2.0.0
- Coder runs: 4
- Review runs: 3
- Gates run: 4 (1 failed)
- Files changed by the agent: 4
- Turns: 63
- Tokens: 4,108,833
- Estimated cost: US$ 2.95

### Agent runs

- coder · claude-haiku-4-5-20251001, claude-sonnet-5 · 431,010 tokens (80,589 in, 324,225 cached, 26,196 out) · 12 turns · US$ 0.65
- plan reviewer · claude-haiku-4-5-20251001, claude-sonnet-5 · 188,576 tokens (40,036 in, 141,765 cached, 6,775 out) · 11 turns · US$ 0.26
- coder · claude-sonnet-5 · 444,745 tokens (113,580 in, 326,244 cached, 4,921 out) · 4 turns · US$ 0.57
- coder · claude-sonnet-5 · 2,499,649 tokens (18,642 in, 2,472,530 cached, 8,477 out) · 20 turns · US$ 0.65
- reviewer · claude-haiku-4-5-20251001, claude-sonnet-5 · 341,012 tokens (62,802 in, 272,662 cached, 5,548 out) · 6 turns · US$ 0.39
- security · claude-haiku-4-5-20251001, claude-sonnet-5 · 63,930 tokens (56,232 in, 6,534 cached, 1,164 out) · 2 turns · US$ 0.27
- analyst · claude-haiku-4-5-20251001 · 53,637 tokens (29,781 in, 19,381 cached, 4,475 out) · 4 turns · US$ 0.09
- changelog · claude-haiku-4-5-20251001 · 86,274 tokens (22,468 in, 62,292 cached, 1,514 out) · 4 turns · US$ 0.06


# Code review fix 1 — 2026-10-01 13:08:53Z

Outcome: **done**

## 1. code review fix 1

The code review asked for changes:

> - **[Blocking] Public constructors bypass validation** (`src/main/java/com/marketdata/sdk/options/ExpirationFilter.java:138-146`)
> The factories enforce bounds, range order, and a non-null operator, but the new public record constructors do not. Callers can construct `new DteRange(45, 0)`, `new DteList(List.of(-1))`, or `new DteComparison(null, -1)` and pass them to `OptionsResource`, bypassing the advertised invariants. Add compact constructors that perform the validation and have the factories delegate to them.
> Evidence: `src/main/java/com/marketdata/sdk/options/ExpirationFilter.java:138-146` adds public records whose canonical constructors lack the checks performed by lines 60-96.
> - **[Should fix] Resource documentation omits new filters**
> The resource page still documents only `ExpirationFilter.dte(int)`, while this change adds three public filter forms. Document `dteList`, `dteRange`, and `dteComparison`, including their wire semantics and bounds, in this pull request.
> Evidence: The diff adds the public factories at `src/main/java/com/marketdata/sdk/options/ExpirationFilter.java:60-96`, but the changed-file list contains no update to `docs/options/chain.md`; the pull request's reviewer notes also state that this page only shows `dte(int)`.

Coder (20 turns, US$ 0.39):

> All changed files are within the allowed limits (`src/main/java/**`, `src/test/java/**`, `docs/**`). Not committing, per instructions.
>
> Summary:
> - **Public constructors bypass validation (Blocking)**: Added compact constructors to `DteList`, `DteRange`, and `DteComparison` records in `ExpirationFilter.java` that perform the same non-negative/≤36500/min≤max/non-null-operator checks previously only enforced by the factory methods. The factories now delegate directly to the record constructors instead of duplicating validation. Added three new tests in `OptionsResourceTest.java` that construct these records directly (bypassing the factories) to prove the invariants now hold at the constructor level.
> - **Resource documentation omits new filters (Should fix)**: Updated `docs/options/chain.md` to document `dteList`, `dteRange`, and `dteComparison` alongside their wire forms (`?dte=15,30,45`, `?dte=0-45`, `?dte=>=30`) and bounds (non-negative, ≤36500 days, `min <= max` for range, `DteOperator` values).
>
> `gradle test` and `gradle build` both pass.

Gates:

- tests, attempt 1: passed
- build, attempt 1: passed

## 2. review 1

Reviewer (2 turns, US$ 0.25): approved

> The fix correctly moves bounds/order/null validation into compact constructors for DteRange, DteList, and DteComparison, closing the exact bypass paths (new DteRange(45,0), new DteList(List.of(-1)), new DteComparison(null,-1)) named in the blocking finding; factories now delegate without duplicating checks, and the added tests exercise each canonical constructor directly and would fail without the fix. The docs update adds the three new filter forms with their bounds, adequately addressing the "should fix" documentation gap. No tests were weakened or removed, and no public-API or behavior regressions were introduced.</summary>
> </invoke>

- [minor] docs/options/chain.md — The dteList doc comment omits the API's "closest expiration for each value, deduplicated" semantic that the matching ExpirationFilter.dteList javadoc states explicitly — a consumer reading only this resource page would not know duplicates collapse.

Security (2 turns, US$ 0.21): approved

> The fix correctly moves bounds/null validation into the compact constructors of DteList, DteRange, and DteComparison so that direct record construction (bypassing the static factories) can no longer produce invalid state, resolving the blocking finding; the factories now simply delegate to the validating constructors with no duplicated or weakened logic. The docs update adds the three new filter forms and their bounds, resolving the second finding, and the new tests exercise the compact constructors directly without removing or weakening any prior coverage. No injection, secrets, or unsafe-default concerns arise since all validated values are plain ints rendered into query strings elsewhere in already-reviewed code.

- no findings


# Code review fix 2 — 2026-10-01 13:21:56Z

Outcome: **done**

## 1. code review fix 2

The code review asked for changes:

> - **[Blocking] Public constructors bypass validation** (`src/main/java/com/marketdata/sdk/options/ExpirationFilter.java:56-132`)
> The upper bound is enforced by `dte(int)`, but the public `new ExpirationFilter.Dte(36501)` constructor still creates an invalid filter that `OptionsResource` sends. Add a validating compact constructor to `Dte`, delegate the factory to it, and cover direct construction with a regression test.
> Evidence: `ExpirationFilter.java:56-60` validates the factory, while `ExpirationFilter.java:132` remains `record Dte(int days) implements ExpirationFilter {}`.

Coder (10 turns, US$ 0.18):

> Both `test` and `build` pass.
>
> **[Blocking] Public constructors bypass validation** — Added a validating compact constructor to the `Dte` record (`ExpirationFilter.java`), enforcing the same non-negative/≤36500 bounds that `DteList`/`DteRange`/`DteComparison` already had from fix round 1. The `dte(int)` factory now just delegates to `new Dte(days)` instead of duplicating the check. Added `dteRecordConstructorRejectsAbove36500` in `OptionsResourceTest.java`, proving `new ExpirationFilter.Dte(36501)` now throws directly, alongside the existing sibling tests for the other three records.

Gates:

- tests, attempt 1: passed
- build, attempt 1: passed

## 2. review 1

Reviewer (2 turns, US$ 0.19): approved

> The fix correctly moves validation into a compact constructor on the Dte record and simplifies the dte(int) factory to delegate to it, closing the bypass where direct construction skipped the upper-bound check. The added test directly exercises `new ExpirationFilter.Dte(36501)` as requested by the finding, and no existing tests were weakened. This matches the reviewer's prescribed fix exactly.</summary>
> </invoke>

- [minor] src/test/java/com/marketdata/sdk/OptionsResourceTest.java — The new regression test only covers direct construction with a too-large value; there's no direct-construction test for a negative Dte (e.g. new Dte(-1)) to fully mirror the negative-value coverage that already exists for the factory path.

Security (2 turns, US$ 0.04): approved

> The fix correctly closes the validation-bypass hole by moving the bound check into Dte's compact constructor so both the dte(int) factory and direct `new ExpirationFilter.Dte(...)` construction enforce MAX_DTE_DAYS; the factory's redundant explicit call was removed since the record now validates itself. A regression test (dteRecordConstructorRejectsAbove36500) covers the previously-exploitable path. No injection, secrets, dependency, or unrelated-change concerns in this diff — it's a narrowly scoped, correct fix.

- no findings
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **Options `dte` parameter enhancements** (API 2.0.0) — `ExpirationFilter` now
supports comma-separated lists (`dteList`), ranges (`dteRange`), and comparison
operators (`dteComparison` with `DteOperator.GT/GTE/LT/LTE`) for richer
expiration filtering. `MAX_DTE_DAYS` constant (36500) documents the valid range.

### Changed

- **Options `dte(int)` validation** (API 2.0.0) — now enforces an upper bound of
36500 days; calls passing larger values will throw `BadRequestError`. Migrate
by clamping values to the valid range or using the new `dteRange` / `dteList`
factories.

## [1.0.0] - 2026-06-29

First stable release of the Market Data Java &amp; Kotlin SDK — a single JVM
Expand Down
Loading
Loading