Skip to content

[FAILED] sdk-sync: API 2.0.0+ffb34b7 - #87

Closed
marketdata-sdk-sync[bot] wants to merge 1 commit into
mainfrom
sdk-sync/2.0.0-ffb34b7
Closed

marketdata-sdk-sync[bot] wants to merge 1 commit into
mainfrom
sdk-sync/2.0.0-ffb34b7

Conversation

@marketdata-sdk-sync

@marketdata-sdk-sync marketdata-sdk-sync Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Failed. The pipeline could not finish this change: the analyst did not finish: the analyst returned an answer the pipeline cannot read: the answer is not an object. This draft is what the coder produced before the pipeline stopped; nothing in it passed the gates. The full story is in the job log comment on this pull request and in the failures/ folder of the state repository.
Generated by sdk-auto-sync for API release 2.0.0 (commit ffb34b7).

Changes in the API surface

Snapshots ae2b047c8f6f → c2a31e277345:

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

What the agent did

tests: Confirmed: the build fails as expected (missing DteFilter, which phase 2 implements). Command was run exactly once.

I added 17 tests to OptionsResourceTest.java (4 wire, 1 regression, 7 validation, 1 parity) and OptionsIntegrationTest.java (4 integration), matching the reviewed plan exactly, plus a DteFilter import in each file. No production code or existing lines were touched; gradle test --no-daemon --console=plain fails as required, on the not-yet-existing DteFilter symbol.

implement: Good — existing ExpirationFilter.dte(int) is untouched (same error for negative values), and the new overload is purely additive. Build and tests are clean.

Added a DteFilter sealed type (exact/values/range/comparison, mirroring StrikeFilter) and a new ExpirationFilter.dte(DteFilter) overload plus its DteExpression variant, wired into OptionsResource's chain-query builder. The pre-existing ExpirationFilter.dte(int) overload is untouched, so old callers keep compiling and failing the same way. One test from the plan was corrected: the comma-separated wire assertion now expects %2C (the query encoder percent-encodes , like it already does >=), not a literal comma. gradle test and gradle build both pass, including Spotless and JaCoCo coverage verification.

review fix 1: All findings addressed:

  • docs/options/chain.md: added the ExpirationFilter.dte(DteFilter) entry to the builder listing, a one-line description of DteFilter's four forms, and a Java example (DteFilter.range) plus a Kotlin example (DteFilter.comparison).
  • OptionsResourceTest.java comparison coverage: replaced the single-operator wire test with a @ParameterizedTest over all four DteFilter.Operator constants, asserting each encoded prefix (%3E, %3E%3D, %3C, %3C%3D).
  • DteFilter.Values (minor): added a compact constructor that copies to an immutable list and validates every element, so a caller building Values directly (not through values(...)) can no longer bypass bounds/empty checks or hold a mutable/null-containing list; the factory method was simplified since validation now lives in one place.
  • Duplicate regression test (minor): dropped chainExpirationFilterDteIntOverloadStillTranslatesToSingleValue — it was byte-for-byte identical to the pre-existing chainExpirationFilterDteTranslatesToDteParam, which already guards that regression.

gradle test and gradle build both pass, including Spotless and JaCoCo coverage verification.

Files

  • docs/options/chain.md
  • src/integrationTest/java/com/marketdata/sdk/OptionsIntegrationTest.java
  • src/main/java/com/marketdata/sdk/OptionsResource.java
  • src/main/java/com/marketdata/sdk/options/DteFilter.java
  • src/main/java/com/marketdata/sdk/options/ExpirationFilter.java
  • src/test/java/com/marketdata/sdk/OptionsResourceTest.java

Test plan

17 case(s) (wire 4, regression 1, validation 7, parity 1, integration 4)

# category test API form input expected
1 wire chainDteFilterExactTranslatesToSingleValue a single value ExpirationFilter.dte(DteFilter.exact(30)) on OptionsChainRequest.expirationFilter, via options.chainAsync(...).join() uri equals http://localhost/v1/options/chain/AAPL/?dte=30
2 wire chainDteFilterValuesTranslatesToCommaSeparatedList a comma-separated list (15,30,45) ExpirationFilter.dte(DteFilter.values(15, 30, 45)) on OptionsChainRequest.expirationFilter uri equals http://localhost/v1/options/chain/AAPL/?dte=15,30,45
3 wire chainDteFilterRangeTranslatesToClosedRange a closed range (0-45) ExpirationFilter.dte(DteFilter.range(0, 45)) on OptionsChainRequest.expirationFilter uri equals http://localhost/v1/options/chain/AAPL/?dte=0-45
4 wire chainDteFilterComparisonTranslatesToOperatorPrefix a comparison operator (>=30, <45) ExpirationFilter.dte(DteFilter.comparison(DteFilter.Operator.GTE, 30)) on OptionsChainRequest.expirationFilter uri string contains dte=%3E%3D30
5 regression chainExpirationFilterDteIntOverloadStillTranslatesToSingleValue a single value (pre-existing int overload) ExpirationFilter.dte(30) on OptionsChainRequest.expirationFilter, unchanged since before 2.0.0 uri equals http://localhost/v1/options/chain/AAPL/?dte=30
6 validation dteFilterExactRejectsBelowMin a single value DteFilter.exact(-1) IllegalArgumentException "days must be in 0..36500"
7 validation dteFilterExactRejectsAboveMax a single value DteFilter.exact(36501) IllegalArgumentException "days must be in 0..36500"
8 validation dteFilterRangeRejectsMinGreaterThanMax a closed range DteFilter.range(45, 0) IllegalArgumentException "min must be <= max"
9 validation dteFilterRangeRejectsOutOfBoundsMin a closed range DteFilter.range(-1, 45) IllegalArgumentException "days must be in 0..36500"
10 validation dteFilterValuesRejectsEmptyList a comma-separated list DteFilter.values() IllegalArgumentException "values must not be empty"
11 validation dteFilterValuesRejectsOutOfBoundsValue a comma-separated list DteFilter.values(15, -1, 45) IllegalArgumentException "days must be in 0..36500"
12 validation dteFilterComparisonRejectsAboveMax a comparison operator DteFilter.comparison(DteFilter.Operator.LT, 36501) IllegalArgumentException "days must be in 0..36500"
13 parity chainDteFilterRangeSyncAndAsyncProduceIdenticalRequest a closed range (0-45) options.chain(req) and options.chainAsync(req).join() with ExpirationFilter.dte(DteFilter.range(0, 45)) both captured request URIs equal http://localhost/v1/options/chain/AAPL/?dte=0-45
14 integration chainDteFilterExactReturnsSingleClosestExpiration a single value client.options().chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(DteFilter.exact(30))).side(OptionSide.CALL).build()) resp.values() not empty; distinct OptionQuote::expiration count equals 1
15 integration chainDteFilterValuesReturnsAtMostOneExpirationPerRequestedValue a comma-separated list (15,30,45) client.options().chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(DteFilter.values(15, 30, 45))).side(OptionSide.CALL).strikeLimit(1).build()) resp.values() not empty; distinct OptionQuote::expiration count is between 1 and 3
16 integration chainDteFilterRangeReturnsExpirationsWithinBounds a closed range (0-45) client.options().chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(DteFilter.range(0, 45))).side(OptionSide.CALL).build()) resp.values() not empty; every row's dte() is between 0 and 45 inclusive
17 integration chainDteFilterComparisonReturnsExpirationsAboveThreshold a comparison operator (>=30, <45) client.options().chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(DteFilter.comparison(DteFilter.Operator.GTE, 30))).side(OptionSide.CALL).build()) resp.values() not empty; every row's dte() is >= 30

Live-API suite

  • src/integrationTest/java/com/marketdata/sdk/OptionsIntegrationTest.java

Files of this suite changed by this pull request. The pipeline did not run the suite (it has no API token); the repository runs it with /integrationtest.

Job log

The full record of this job — every phase, test and build result, review findings and how they were resolved, and each fix round after the PR was opened — is in the job log comment.


5 coder run(s) · 6 review(s) · 129 turns · 11,049,750 tokens · US$ 8.07 · item 5c097984-5f7b-401b-abba-235a3f417767

Agent runs
  • coder · claude-haiku-4-5-20251001, claude-sonnet-5 · 1,277,078 tokens (94,083 in, 1,149,928 cached, 33,067 out) · 24 turns · US$ 0.94
  • plan reviewer · claude-haiku-4-5-20251001, claude-opus-5 · 242,141 tokens (40,157 in, 195,937 cached, 6,047 out) · 14 turns · US$ 0.65
  • coder · claude-sonnet-5 · 100,803 tokens (3,581 in, 94,045 cached, 3,177 out) · 2 turns · US$ 0.06
  • plan reviewer · claude-haiku-4-5-20251001, claude-opus-5 · 36,733 tokens (11,683 in, 24,293 cached, 757 out) · 3 turns · US$ 0.15
  • coder · claude-sonnet-5 · 1,160,307 tokens (122,626 in, 1,028,172 cached, 9,509 out) · 10 turns · US$ 0.79
  • coder · claude-sonnet-5 · 2,890,627 tokens (19,230 in, 2,862,975 cached, 8,422 out) · 22 turns · US$ 0.73
  • reviewer · claude-haiku-4-5-20251001, claude-opus-5 · 1,026,360 tokens (95,050 in, 919,923 cached, 11,387 out) · 18 turns · US$ 1.74
  • security · claude-haiku-4-5-20251001, claude-sonnet-5 · 79,224 tokens (78,486 in, 0 cached, 738 out) · 2 turns · US$ 0.37
  • coder · claude-sonnet-5 · 3,436,142 tokens (24,139 in, 3,399,420 cached, 12,583 out) · 22 turns · US$ 0.90
  • reviewer · claude-haiku-4-5-20251001, claude-opus-5 · 448,972 tokens (85,473 in, 355,037 cached, 8,462 out) · 7 turns · US$ 1.29
  • security · claude-haiku-4-5-20251001, claude-sonnet-5 · 351,363 tokens (75,341 in, 274,043 cached, 1,979 out) · 5 turns · US$ 0.43

tests: Confirmed: the build fails as expected (missing `DteFilter`, which phase 2 implements). Command was run exactly once.

I added 17 tests to `OptionsResourceTest.java` (4 wire, 1 regression, 7 validation, 1 parity) and `OptionsIntegrationTest.java` (4 integration), matching the reviewed plan exactly, plus a `DteFilter` import in each file. No production code or existing lines were touched; `gradle test --no-daemon --console=plain` fails as required, on the not-yet-existing `DteFilter` symbol.

implement: Good — existing `ExpirationFilter.dte(int)` is untouched (same error for negative values), and the new overload is purely additive. Build and tests are clean.

Added a `DteFilter` sealed type (exact/values/range/comparison, mirroring `StrikeFilter`) and a new `ExpirationFilter.dte(DteFilter)` overload plus its `DteExpression` variant, wired into `OptionsResource`'s chain-query builder. The pre-existing `ExpirationFilter.dte(int)` overload is untouched, so old callers keep compiling and failing the same way. One test from the plan was corrected: the comma-separated wire assertion now expects `%2C` (the query encoder percent-encodes `,` like it already does `>=`), not a literal comma. `gradle test` and `gradle build` both pass, including Spotless and JaCoCo coverage verification.

review fix 1: All findings addressed:

- docs/options/chain.md: added the `ExpirationFilter.dte(DteFilter)` entry to the builder listing, a one-line description of `DteFilter`'s four forms, and a Java example (`DteFilter.range`) plus a Kotlin example (`DteFilter.comparison`).
- OptionsResourceTest.java comparison coverage: replaced the single-operator wire test with a `@ParameterizedTest` over all four `DteFilter.Operator` constants, asserting each encoded prefix (`%3E`, `%3E%3D`, `%3C`, `%3C%3D`).
- DteFilter.Values (minor): added a compact constructor that copies to an immutable list and validates every element, so a caller building `Values` directly (not through `values(...)`) can no longer bypass bounds/empty checks or hold a mutable/null-containing list; the factory method was simplified since validation now lives in one place.
- Duplicate regression test (minor): dropped `chainExpirationFilterDteIntOverloadStillTranslatesToSingleValue` — it was byte-for-byte identical to the pre-existing `chainExpirationFilterDteTranslatesToDteParam`, which already guards that regression.

`gradle test` and `gradle build` both pass, including Spotless and JaCoCo coverage verification.
@marketdata-sdk-sync marketdata-sdk-sync Bot added the sdk-sync: needs human sdk-sync stopped: a person has to look (see the PR's comments) label Oct 5, 2026
@marketdata-sdk-sync

Copy link
Copy Markdown
Author

🧾 Job log — sdk-sync

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-05 13:39:00Z
Outcome failed — the analyst did not finish: the analyst returned an answer the pipeline cannot read: the answer is not an object

Changes in the API surface

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

1. test plan

Coder (24 turns, US$ 0.94) — test plan, 17 case(s) (wire 4, regression 1, validation 7, parity 1, integration 4):

Adds a sealed DteFilter type (options package) mirroring StrikeFilter with Exact/Values/Range/Comparison variants, plus a new ExpirationFilter.dte(DteFilter) overload returning a new permitted DteExpression variant. The existing ExpirationFilter.dte(int) overload and its Dte record are left untouched (regression).

# category test API form input expected
1 wire chainDteFilterExactTranslatesToSingleValue a single value ExpirationFilter.dte(DteFilter.exact(30)) on OptionsChainRequest.expirationFilter, via options.chainAsync(...).join() uri equals http://localhost/v1/options/chain/AAPL/?dte=30
2 wire chainDteFilterValuesTranslatesToCommaSeparatedList a comma-separated list (15,30,45) ExpirationFilter.dte(DteFilter.values(15, 30, 45)) on OptionsChainRequest.expirationFilter uri equals http://localhost/v1/options/chain/AAPL/?dte=15,30,45
3 wire chainDteFilterRangeTranslatesToClosedRange a closed range (0-45) ExpirationFilter.dte(DteFilter.range(0, 45)) on OptionsChainRequest.expirationFilter uri equals http://localhost/v1/options/chain/AAPL/?dte=0-45
4 wire chainDteFilterComparisonTranslatesToOperatorPrefix a comparison operator (>=30, <45) ExpirationFilter.dte(DteFilter.comparison(DteFilter.Operator.GTE, 30)) on OptionsChainRequest.expirationFilter uri string contains dte=%3E%3D30
5 regression chainExpirationFilterDteIntOverloadStillTranslatesToSingleValue a single value (pre-existing int overload) ExpirationFilter.dte(30) on OptionsChainRequest.expirationFilter, unchanged since before 2.0.0 uri equals http://localhost/v1/options/chain/AAPL/?dte=30
6 validation dteFilterExactRejectsBelowMin a single value DteFilter.exact(-1) IllegalArgumentException "days must be in 0..36500"
7 validation dteFilterExactRejectsAboveMax a single value DteFilter.exact(36501) IllegalArgumentException "days must be in 0..36500"
8 validation dteFilterRangeRejectsMinGreaterThanMax a closed range DteFilter.range(45, 0) IllegalArgumentException "min must be <= max"
9 validation dteFilterRangeRejectsOutOfBoundsMin a closed range DteFilter.range(-1, 45) IllegalArgumentException "days must be in 0..36500"
10 validation dteFilterValuesRejectsEmptyList a comma-separated list DteFilter.values() IllegalArgumentException "values must not be empty"
11 validation dteFilterValuesRejectsOutOfBoundsValue a comma-separated list DteFilter.values(15, -1, 45) IllegalArgumentException "days must be in 0..36500"
12 validation dteFilterComparisonRejectsAboveMax a comparison operator DteFilter.comparison(DteFilter.Operator.LT, 36501) IllegalArgumentException "days must be in 0..36500"
13 parity chainDteFilterRangeSyncAndAsyncProduceIdenticalRequest a closed range (0-45) options.chain(req) and options.chainAsync(req).join() with ExpirationFilter.dte(DteFilter.range(0, 45)) both captured request URIs equal http://localhost/v1/options/chain/AAPL/?dte=0-45
14 integration chainDteFilterExactReturnsNonEmptyChainWithDtePopulated a single value client.options().chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(DteFilter.exact(30))).side(OptionSide.CALL).strikeLimit(1).build()) resp.values() not empty; every row's dte() non-null and finite
15 integration chainDteFilterValuesReturnsAtMostOneExpirationPerRequestedValue a comma-separated list (15,30,45) client.options().chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(DteFilter.values(15, 30, 45))).side(OptionSide.CALL).strikeLimit(1).build()) resp.values() not empty; distinct OptionQuote::expiration count is between 1 and 3
16 integration chainDteFilterRangeReturnsExpirationsWithinBounds a closed range (0-45) client.options().chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(DteFilter.range(0, 45))).side(OptionSide.CALL).build()) resp.values() not empty; every row's dte() is between 0 and 45 inclusive
17 integration chainDteFilterComparisonReturnsExpirationsAboveThreshold a comparison operator (>=30, <45) client.options().chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(DteFilter.comparison(DteFilter.Operator.GTE, 30))).side(OptionSide.CALL).build()) resp.values() not empty; every row's dte() is >= 30

Plan reviewer (14 turns, US$ 0.65): 1 blocking finding(s)

All four forms the description enumerates (single value, comma list, closed range, comparison operator) have wire cases and invent nothing beyond them; the pre-existing ExpirationFilter.dte(int) regression, bounds/ordering/empty validations on the new DteFilter, sync+async parity, and per-form integration cases are all present and placed in the right files. No response case is needed (no new endpoint, OptionQuote.dte() unchanged). One integration assertion is too weak to demonstrate its form.

  • [major] src/integrationTest/java/com/marketdata/sdk/OptionsIntegrationTest.java — Case 14 (chainDteFilterExactReturnsNonEmptyChainWithDtePopulated) asserts only that every row's dte() is non-null and finite — true of any chain response, so it never proves the single-value form reached the wire. Assert the filter's distinguishing property instead: exactly one distinct OptionQuote::expiration (a single dte selects the closest single expiration), mirroring chainExpirationAllSpansMultipleExpirations.

2. test plan (revised 1)

Coder (2 turns, US$ 0.06) — test plan, 17 case(s) (wire 4, regression 1, validation 7, parity 1, integration 4):

Adds a sealed DteFilter type (options package) mirroring StrikeFilter with Exact/Values/Range/Comparison variants, plus a new ExpirationFilter.dte(DteFilter) overload returning a new permitted DteExpression variant. The existing ExpirationFilter.dte(int) overload and its Dte record are left untouched (regression).

# category test API form input expected
1 wire chainDteFilterExactTranslatesToSingleValue a single value ExpirationFilter.dte(DteFilter.exact(30)) on OptionsChainRequest.expirationFilter, via options.chainAsync(...).join() uri equals http://localhost/v1/options/chain/AAPL/?dte=30
2 wire chainDteFilterValuesTranslatesToCommaSeparatedList a comma-separated list (15,30,45) ExpirationFilter.dte(DteFilter.values(15, 30, 45)) on OptionsChainRequest.expirationFilter uri equals http://localhost/v1/options/chain/AAPL/?dte=15,30,45
3 wire chainDteFilterRangeTranslatesToClosedRange a closed range (0-45) ExpirationFilter.dte(DteFilter.range(0, 45)) on OptionsChainRequest.expirationFilter uri equals http://localhost/v1/options/chain/AAPL/?dte=0-45
4 wire chainDteFilterComparisonTranslatesToOperatorPrefix a comparison operator (>=30, <45) ExpirationFilter.dte(DteFilter.comparison(DteFilter.Operator.GTE, 30)) on OptionsChainRequest.expirationFilter uri string contains dte=%3E%3D30
5 regression chainExpirationFilterDteIntOverloadStillTranslatesToSingleValue a single value (pre-existing int overload) ExpirationFilter.dte(30) on OptionsChainRequest.expirationFilter, unchanged since before 2.0.0 uri equals http://localhost/v1/options/chain/AAPL/?dte=30
6 validation dteFilterExactRejectsBelowMin a single value DteFilter.exact(-1) IllegalArgumentException "days must be in 0..36500"
7 validation dteFilterExactRejectsAboveMax a single value DteFilter.exact(36501) IllegalArgumentException "days must be in 0..36500"
8 validation dteFilterRangeRejectsMinGreaterThanMax a closed range DteFilter.range(45, 0) IllegalArgumentException "min must be <= max"
9 validation dteFilterRangeRejectsOutOfBoundsMin a closed range DteFilter.range(-1, 45) IllegalArgumentException "days must be in 0..36500"
10 validation dteFilterValuesRejectsEmptyList a comma-separated list DteFilter.values() IllegalArgumentException "values must not be empty"
11 validation dteFilterValuesRejectsOutOfBoundsValue a comma-separated list DteFilter.values(15, -1, 45) IllegalArgumentException "days must be in 0..36500"
12 validation dteFilterComparisonRejectsAboveMax a comparison operator DteFilter.comparison(DteFilter.Operator.LT, 36501) IllegalArgumentException "days must be in 0..36500"
13 parity chainDteFilterRangeSyncAndAsyncProduceIdenticalRequest a closed range (0-45) options.chain(req) and options.chainAsync(req).join() with ExpirationFilter.dte(DteFilter.range(0, 45)) both captured request URIs equal http://localhost/v1/options/chain/AAPL/?dte=0-45
14 integration chainDteFilterExactReturnsSingleClosestExpiration a single value client.options().chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(DteFilter.exact(30))).side(OptionSide.CALL).build()) resp.values() not empty; distinct OptionQuote::expiration count equals 1
15 integration chainDteFilterValuesReturnsAtMostOneExpirationPerRequestedValue a comma-separated list (15,30,45) client.options().chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(DteFilter.values(15, 30, 45))).side(OptionSide.CALL).strikeLimit(1).build()) resp.values() not empty; distinct OptionQuote::expiration count is between 1 and 3
16 integration chainDteFilterRangeReturnsExpirationsWithinBounds a closed range (0-45) client.options().chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(DteFilter.range(0, 45))).side(OptionSide.CALL).build()) resp.values() not empty; every row's dte() is between 0 and 45 inclusive
17 integration chainDteFilterComparisonReturnsExpirationsAboveThreshold a comparison operator (>=30, <45) client.options().chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(DteFilter.comparison(DteFilter.Operator.GTE, 30))).side(OptionSide.CALL).build()) resp.values() not empty; every row's dte() is >= 30

Plan reviewer (3 turns, US$ 0.15): approved

Case 14 now asserts exactly one distinct OptionQuote::expiration, mirroring chainExpirationAllSpansMultipleExpirations, which proves the single-value dte form reached the wire. The prior finding is resolved and the replacement introduces nothing new. Plan approved.

  • no findings

3. tests

Coder (10 turns, US$ 0.79):

Confirmed: the build fails as expected (missing DteFilter, which phase 2 implements). Command was run exactly once.

I added 17 tests to OptionsResourceTest.java (4 wire, 1 regression, 7 validation, 1 parity) and OptionsIntegrationTest.java (4 integration), matching the reviewed plan exactly, plus a DteFilter import in each file. No production code or existing lines were touched; gradle test --no-daemon --console=plain fails as required, on the not-yet-existing DteFilter symbol.

Gates:

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

4. implement

Coder (22 turns, US$ 0.73):

Good — existing ExpirationFilter.dte(int) is untouched (same error for negative values), and the new overload is purely additive. Build and tests are clean.

Added a DteFilter sealed type (exact/values/range/comparison, mirroring StrikeFilter) and a new ExpirationFilter.dte(DteFilter) overload plus its DteExpression variant, wired into OptionsResource's chain-query builder. The pre-existing ExpirationFilter.dte(int) overload is untouched, so old callers keep compiling and failing the same way. One test from the plan was corrected: the comma-separated wire assertion now expects %2C (the query encoder percent-encodes , like it already does >=), not a literal comma. gradle test and gradle build both pass, including Spotless and JaCoCo coverage verification.

Gates:

  • tests, attempt 1: passed
  • build, attempt 1: passed

5. review 1

Reviewer (18 turns, US$ 1.74): 2 blocking finding(s)

The change adds a sealed DteFilter (exact/values/range/comparison) plus an additive ExpirationFilter.dte(DteFilter) overload and a new DteExpression variant, wires it into applyExpirationFilter, and covers all four wire forms, the validation cases and the four live-API cases from the plan; the pre-existing dte(int) path is left untouched, so no public signature changes. Version impact: minor — new type DteFilter, new overload ExpirationFilter.dte(DteFilter), new permitted subtype ExpirationFilter.DteExpression; nothing removed, retyped or re-defaulted (existing dte(int) keeps its IllegalArgumentException). Two gaps block merge: the chain endpoint doc page was not updated for the new public surface, and two constants of the new Operator enum have no test.

  • [major] docs/options/chain.md:23 — The expiration-filter list still shows only ExpirationFilter.dte(int days); the new public ExpirationFilter.dte(DteFilter) overload and the DteFilter type (list/range/comparison forms) are undocumented. Per the repo rule that a new public method and changed parameter get the resource page in the same PR, add the dte(DteFilter) entry and a Java+Kotlin example of at least one of the list/range/comparison forms.
  • [major] src/test/java/com/marketdata/sdk/OptionsResourceTest.java:1045 — Only Operator.GTE ever reaches the wire assertion (dte=%3E%3D30); GT and LTE are never exercised at all and LT only in a validation test, so three of the four constants of the new enum have no test that their wireValue() prefix is emitted. Add a wire assertion per constant (e.g. dte=%3E30, dte=%3C30, dte=%3C%3D30).
  • [minor] src/main/java/com/marketdata/sdk/options/DteFilter.java:97 — Values' implicit public canonical constructor takes the List<Integer> unchecked and uncopied, so new DteFilter.Values(mutableList) bypasses the bounds/empty validation the factory advertises and leaves the record backed by a mutable list (and new Values(null) NPEs later in dteFilterWireValue). Add a compact constructor that does days = List.copyOf(days) and runs validate on each element.
  • [minor] src/test/java/com/marketdata/sdk/OptionsResourceTest.java:980 — chainExpirationFilterDteIntOverloadStillTranslatesToSingleValue is byte-for-byte identical to the pre-existing chainExpirationFilterDteTranslatesToDteParam directly above it (same request, same expected URI), so it adds no coverage. Either drop it or keep one test and note the regression intent in its comment.

Security (2 turns, US$ 0.37): approved

The change adds a typed DteFilter sealed type and wires it into the options chain dte query parameter, with all values originating from validated int/Integer inputs rather than raw strings. No injection, secrets, logging, or dependency surface is introduced; query encoding (percent-escaping of ,, >, <, -) is handled by existing unchanged infrastructure. No security issues found in the files reviewed.

  • no findings

6. review fix 1

Findings sent back to the coder:

  • [major] docs/options/chain.md:23 — The expiration-filter list still shows only ExpirationFilter.dte(int days); the new public ExpirationFilter.dte(DteFilter) overload and the DteFilter type (list/range/comparison forms) are undocumented. Per the repo rule that a new public method and changed parameter get the resource page in the same PR, add the dte(DteFilter) entry and a Java+Kotlin example of at least one of the list/range/comparison forms.
  • [major] src/test/java/com/marketdata/sdk/OptionsResourceTest.java:1045 — Only Operator.GTE ever reaches the wire assertion (dte=%3E%3D30); GT and LTE are never exercised at all and LT only in a validation test, so three of the four constants of the new enum have no test that their wireValue() prefix is emitted. Add a wire assertion per constant (e.g. dte=%3E30, dte=%3C30, dte=%3C%3D30).

Minor findings sent along, as optional:

  • [minor] src/main/java/com/marketdata/sdk/options/DteFilter.java:97 — Values' implicit public canonical constructor takes the List<Integer> unchecked and uncopied, so new DteFilter.Values(mutableList) bypasses the bounds/empty validation the factory advertises and leaves the record backed by a mutable list (and new Values(null) NPEs later in dteFilterWireValue). Add a compact constructor that does days = List.copyOf(days) and runs validate on each element.
  • [minor] src/test/java/com/marketdata/sdk/OptionsResourceTest.java:980 — chainExpirationFilterDteIntOverloadStillTranslatesToSingleValue is byte-for-byte identical to the pre-existing chainExpirationFilterDteTranslatesToDteParam directly above it (same request, same expected URI), so it adds no coverage. Either drop it or keep one test and note the regression intent in its comment.

Coder (22 turns, US$ 0.90):

All findings addressed:

  • docs/options/chain.md: added the ExpirationFilter.dte(DteFilter) entry to the builder listing, a one-line description of DteFilter's four forms, and a Java example (DteFilter.range) plus a Kotlin example (DteFilter.comparison).
  • OptionsResourceTest.java comparison coverage: replaced the single-operator wire test with a @ParameterizedTest over all four DteFilter.Operator constants, asserting each encoded prefix (%3E, %3E%3D, %3C, %3C%3D).
  • DteFilter.Values (minor): added a compact constructor that copies to an immutable list and validates every element, so a caller building Values directly (not through values(...)) can no longer bypass bounds/empty checks or hold a mutable/null-containing list; the factory method was simplified since validation now lives in one place.
  • Duplicate regression test (minor): dropped chainExpirationFilterDteIntOverloadStillTranslatesToSingleValue — it was byte-for-byte identical to the pre-existing chainExpirationFilterDteTranslatesToDteParam, which already guards that regression.

gradle test and gradle build both pass, including Spotless and JaCoCo coverage verification.

Gates:

  • tests, attempt 1: passed
  • build, attempt 1: passed

7. review 2

Reviewer (7 turns, US$ 1.29): approved

The cycle-1 findings are addressed: docs/options/chain.md now documents the dte(DteFilter) overload with Java and Kotlin examples, the comparison wire test is parameterized over all four Operator constants, and Values validates and copies in a compact constructor. Wire forms check out against HttpTransport.encodeQueryComponent (,→%2C, - literal, >/</= percent-encoded), and the public surface is purely additive (new DteFilter type, new DteExpression sealed variant, new dte(DteFilter) overload — version impact: minor). Test-plan case 5 (the int-overload regression) was deleted at my cycle-1 invitation and its assertion is still made verbatim by the pre-existing chainExpirationFilterDteTranslatesToDteParam, so no coverage was lost.

  • [minor] src/main/java/com/marketdata/sdk/options/DteFilter.java:25 — MAX_DAYS is a new public constant with no doc comment of its own (the javadoc above attaches only to MIN_DAYS). Add a one-line comment naming the API's 36500-day ceiling, or document the pair together.

Security (5 turns, US$ 0.43): approved

Test

  • no findings

Totals

  • Version: not assessed
  • Coder runs: 5
  • Review runs: 6
  • Gates run: 5 (1 failed)
  • Files changed by the agent: 6
  • Turns: 129
  • Tokens: 11,049,750
  • Estimated cost: US$ 8.07

Agent runs

  • coder · claude-haiku-4-5-20251001, claude-sonnet-5 · 1,277,078 tokens (94,083 in, 1,149,928 cached, 33,067 out) · 24 turns · US$ 0.94
  • plan reviewer · claude-haiku-4-5-20251001, claude-opus-5 · 242,141 tokens (40,157 in, 195,937 cached, 6,047 out) · 14 turns · US$ 0.65
  • coder · claude-sonnet-5 · 100,803 tokens (3,581 in, 94,045 cached, 3,177 out) · 2 turns · US$ 0.06
  • plan reviewer · claude-haiku-4-5-20251001, claude-opus-5 · 36,733 tokens (11,683 in, 24,293 cached, 757 out) · 3 turns · US$ 0.15
  • coder · claude-sonnet-5 · 1,160,307 tokens (122,626 in, 1,028,172 cached, 9,509 out) · 10 turns · US$ 0.79
  • coder · claude-sonnet-5 · 2,890,627 tokens (19,230 in, 2,862,975 cached, 8,422 out) · 22 turns · US$ 0.73
  • reviewer · claude-haiku-4-5-20251001, claude-opus-5 · 1,026,360 tokens (95,050 in, 919,923 cached, 11,387 out) · 18 turns · US$ 1.74
  • security · claude-haiku-4-5-20251001, claude-sonnet-5 · 79,224 tokens (78,486 in, 0 cached, 738 out) · 2 turns · US$ 0.37
  • coder · claude-sonnet-5 · 3,436,142 tokens (24,139 in, 3,399,420 cached, 12,583 out) · 22 turns · US$ 0.90
  • reviewer · claude-haiku-4-5-20251001, claude-opus-5 · 448,972 tokens (85,473 in, 355,037 cached, 8,462 out) · 7 turns · US$ 1.29
  • security · claude-haiku-4-5-20251001, claude-sonnet-5 · 351,363 tokens (75,341 in, 274,043 cached, 1,979 out) · 5 turns · US$ 0.43

@codecov

codecov Bot commented Oct 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.30%. Comparing base (d744f98) to head (dd3a450).

Additional details and impacted files
@@             Coverage Diff              @@
##               main      #87      +/-   ##
============================================
+ Coverage     98.27%   98.30%   +0.02%     
- Complexity     1043     1057      +14     
============================================
  Files           123      124       +1     
  Lines          2606     2652      +46     
  Branches        301      307       +6     
============================================
+ Hits           2561     2607      +46     
  Partials         45       45              
Files with missing lines Coverage Δ
.../main/java/com/marketdata/sdk/OptionsResource.java 98.59% <100.00%> (+0.06%) ⬆️
...ain/java/com/marketdata/sdk/options/DteFilter.java 100.00% <100.00%> (ø)
...a/com/marketdata/sdk/options/ExpirationFilter.java 95.45% <100.00%> (+0.45%) ⬆️

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d744f98...dd3a450. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@MarketDataDev03

Copy link
Copy Markdown
Collaborator

Closed to rerun the sdk-sync pipeline from scratch for API 2.0.0 (the analyst step is now retried).

@MarketDataDev03
MarketDataDev03 deleted the sdk-sync/2.0.0-ffb34b7 branch October 5, 2026 14:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sdk-sync: needs human sdk-sync stopped: a person has to look (see the PR's comments)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant