Skip to content

sdk-sync: API 2.0.0+ffb34b7 → sdk-java 1.1.0 (minor) - #85

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 2, 2026 •

Copy link
Copy Markdown

Needs human review. The internal review did not pass after 2 cycles, or a reviewer did not finish. This pull request is a draft, labelled sdk-sync: needs human. Left open by the internal review:

  • [major] src/main/java/com/marketdata/sdk/options/ExpirationFilter.java:96 — @Generated excludes the entire dteComparison method from JaCoCo accounting, but the method is reachable and exercised — it also contains the new validateDteBounds(days) call and the record construction, so real new logic is now invisible to the coverage ratchet. Generated's own javadoc restricts it to "code that cannot be exercised by a hermetic unit test". Replace the hand-rolled null guard with Objects.requireNonNull(operator, "operator") — which adds no branch, matching onDate/dteList/between in this same file — and drop the annotation.
  • [major] src/main/java/com/marketdata/sdk/options/ExpirationFilter.java:53 — The pre-existing single-value factory dte(int days) only rejects negative values (dte must be non-negative) and never calls the new validateDteBounds helper, so it still accepts values far above 36500. The three new sibling forms (dteList, dteRange, dteComparison) all enforce the API's documented '0 to 36500' bound for the same dte parameter, so a single-value filter can now silently send an out-of-spec value the API rejects while the other three forms fail fast client-side. Route dte(int) through validateDteBounds for consistent, complete coverage of the updated parameter's documented range.
    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 exactly as expected, due to the not-yet-implemented dteList/dteRange/dteComparison/Operator symbols. As required, I stop here without touching production code.

I added 10 unit tests to OptionsResourceTest.java (4 wire cases, 1 regression, 5 validation) and 1 sync/parity wire test, plus 3 integration tests to OptionsIntegrationTest.java, exactly per the plan's names and files. gradle test --no-daemon --console=plain fails at compileTestJava with "cannot find symbol" for dteList, dteRange, dteComparison, and Operator, confirming no production code exists yet.

implement: Build and tests pass, all changes stay within src/main, src/test, src/integrationTest, and docs. No commit made.

Added ExpirationFilter.dteList/dteRange/dteComparison (plus a nested Operator enum) as new permitted sealed-interface variants for ?dte=, covering the API's list/range/comparison forms while leaving dte(int) and its error behavior untouched. Wired the new variants into OptionsResource's query-building and updated docs/options/chain.md. Fixed one phase-1 wire test (chainExpirationFilterDteListTranslatesToCommaSeparatedDte) that wrongly expected a literal comma; the repo's convention (seen in the columns tests) is to URL-decode before asserting, since commas are percent-encoded on the wire. gradle build passes after spotlessApply reformatted the new code.

review fix 1: Resolved the one finding: added three wire-assertion tests (chainExpirationFilterDteComparisonGtRendersWithOperatorPrefix, ...LtRendersWithOperatorPrefix, ...LteRendersWithOperatorPrefix) in OptionsResourceTest.java, each driving chainAsync with ExpirationFilter.dteComparison for GT, LT, and LTE and asserting the exact percent-encoded wire form (dte=%3E30, dte=%3C45, dte=%3C%3D45). The existing GTE wire test is untouched, so all four Operator constants now each have a dedicated wire-form test that would fail on a wrong wireValue. No production code changed. gradle test and gradle build both pass.

changelog: Added changelog entry under [Unreleased] documenting the new days-to-expiration filters (dteList, dteRange, dteComparison) and the Operator enum for the options chain endpoint, responding to API 2.0.0.

Files

  • CHANGELOG.md
  • 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/ExpirationFilter.java
  • src/test/java/com/marketdata/sdk/OptionsResourceTest.java

Version

Public-surface change computed by the pipeline: minor — next version 1.1.0 (from 1.0.0).

  • added: ExpirationFilter.dteList(List<Integer>)
  • added: ExpirationFilter.dteRange(int, int)
  • added: ExpirationFilter.dteComparison(Operator, int)
  • added: ExpirationFilter.DteList
  • added: ExpirationFilter.DteRange
  • added: ExpirationFilter.DteComparison
  • added: ExpirationFilter.Operator
  • added: ExpirationFilter.Operator.GT
  • added: ExpirationFilter.Operator.GTE
  • added: ExpirationFilter.Operator.LT
  • added: ExpirationFilter.Operator.LTE
  • added: ExpirationFilter.Operator.wireValue()

Test plan

14 case(s) (wire 4, regression 1, validation 5, parity 1, integration 3)

# category test API form input expected
1 wire chainExpirationFilterDteSingleValueTranslatesToDteParam a single value options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(30)).build()).join() captured URI equals http://localhost/v1/options/chain/AAPL/?dte=30
2 wire chainExpirationFilterDteListTranslatesToCommaSeparatedDte a comma-separated list (15,30,45) options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dteList(List.of(15, 30, 45))).build()).join() captured URI equals http://localhost/v1/options/chain/AAPL/?dte=15,30,45
3 wire chainExpirationFilterDteRangeTranslatesToDashedDte a closed range (0-45) options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dteRange(0, 45)).build()).join() captured URI equals http://localhost/v1/options/chain/AAPL/?dte=0-45
4 wire chainExpirationFilterDteComparisonTranslatesToOperatorPrefixedDte a comparison operator (>=30, <45) options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dteComparison(ExpirationFilter.Operator.GTE, 30)).build()).join() captured URI contains dte=%3E%3D30 (URL-encoded >=30)
5 regression expirationFilterDteSingleValueStillReturnsDteRecordWithDaysAccessor a single value ExpirationFilter.dte(30) instanceof ExpirationFilter.Dte; ((ExpirationFilter.Dte) filter).days() == 30
6 validation expirationFilterDteListRejectsEmptyList a comma-separated list (15,30,45) ExpirationFilter.dteList(List.of()) IllegalArgumentException "days must not be empty"
7 validation expirationFilterDteListRejectsValueOutOfBounds a comma-separated list (15,30,45) ExpirationFilter.dteList(List.of(15, -1)) IllegalArgumentException "dte must be between 0 and 36500"
8 validation expirationFilterDteRangeRejectsMinGreaterThanMax a closed range (0-45) ExpirationFilter.dteRange(45, 0) IllegalArgumentException "min must be <= max"
9 validation expirationFilterDteRangeRejectsValueOutOfBounds a closed range (0-45) ExpirationFilter.dteRange(0, 40000) IllegalArgumentException "dte must be between 0 and 36500"
10 validation expirationFilterDteComparisonRejectsValueOutOfBounds a comparison operator (>=30, <45) ExpirationFilter.dteComparison(ExpirationFilter.Operator.LT, -5) IllegalArgumentException "dte must be between 0 and 36500"
11 parity chainSyncExpirationFilterDteComparisonTranslatesToOperatorPrefixedDte a comparison operator (>=30, <45) options.chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dteComparison(ExpirationFilter.Operator.GTE, 30)).build()) (sync, no .join()) captured URI contains dte=%3E%3D30, same as the async form
12 integration chainDteListNarrowsToClosestExpirationsPerValue a comma-separated list (15,30,45) client.options().chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dteList(List.of(7, 30, 60))).side(OptionSide.CALL).strikeLimit(1).build()) values() non-empty; every row's dte() non-null; distinct dte() values count is between 1 and 3 (closest-per-value, deduplicated)
13 integration chainDteRangeReturnsExpirationsWithinBounds a closed range (0-45) client.options().chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dteRange(0, 45)).side(OptionSide.CALL).strikeLimit(1).build()) values() non-empty; every row's dte() non-null and between 0 and 45 inclusive
14 integration chainDteComparisonReturnsExpirationsSatisfyingOperator a comparison operator (>=30, <45) client.options().chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dteComparison(ExpirationFilter.Operator.GTE, 30)).side(OptionSide.CALL).strikeLimit(1).build()) values() non-empty; every row's dte() non-null and >= 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) · 5 review(s) · 113 turns · 8,424,904 tokens · US$ 7.02 · item 5c097984-5f7b-401b-abba-235a3f417767

Agent runs
  • coder · claude-haiku-4-5-20251001, claude-sonnet-5 · 885,190 tokens (83,228 in, 778,634 cached, 23,328 out) · 20 turns · US$ 0.73
  • plan reviewer · claude-haiku-4-5-20251001, claude-opus-5 · 184,728 tokens (33,179 in, 145,893 cached, 5,656 out) · 13 turns · US$ 0.55
  • coder · claude-sonnet-5 · 729,108 tokens (108,211 in, 616,344 cached, 4,553 out) · 7 turns · US$ 0.60
  • coder · claude-sonnet-5 · 3,818,310 tokens (43,228 in, 3,758,658 cached, 16,424 out) · 29 turns · US$ 1.09
  • reviewer · claude-haiku-4-5-20251001, claude-opus-5 · 397,266 tokens (79,405 in, 307,991 cached, 9,870 out) · 9 turns · US$ 1.24
  • security · claude-haiku-4-5-20251001, claude-sonnet-5 · 79,879 tokens (77,502 in, 0 cached, 2,377 out) · 2 turns · US$ 0.38
  • coder · claude-sonnet-5 · 1,243,300 tokens (6,556 in, 1,233,853 cached, 2,891 out) · 8 turns · US$ 0.30
  • reviewer · claude-haiku-4-5-20251001, claude-opus-5 · 835,104 tokens (84,048 in, 738,729 cached, 12,327 out) · 15 turns · US$ 1.56
  • security · claude-haiku-4-5-20251001, claude-sonnet-5 · 88,298 tokens (66,655 in, 12,291 cached, 9,352 out) · 2 turns · US$ 0.41
  • analyst · claude-haiku-4-5-20251001 · 93,505 tokens (32,533 in, 57,884 cached, 3,088 out) · 4 turns · US$ 0.10
  • changelog · claude-haiku-4-5-20251001 · 70,216 tokens (24,845 in, 43,842 cached, 1,529 out) · 4 turns · US$ 0.06

tests: Confirmed: the build fails exactly as expected, due to the not-yet-implemented `dteList`/`dteRange`/`dteComparison`/`Operator` symbols. As required, I stop here without touching production code.

I added 10 unit tests to `OptionsResourceTest.java` (4 wire cases, 1 regression, 5 validation) and 1 sync/parity wire test, plus 3 integration tests to `OptionsIntegrationTest.java`, exactly per the plan's names and files. `gradle test --no-daemon --console=plain` fails at `compileTestJava` with "cannot find symbol" for `dteList`, `dteRange`, `dteComparison`, and `Operator`, confirming no production code exists yet.

implement: Build and tests pass, all changes stay within `src/main`, `src/test`, `src/integrationTest`, and `docs`. No commit made.

Added `ExpirationFilter.dteList`/`dteRange`/`dteComparison` (plus a nested `Operator` enum) as new permitted sealed-interface variants for `?dte=`, covering the API's list/range/comparison forms while leaving `dte(int)` and its error behavior untouched. Wired the new variants into `OptionsResource`'s query-building and updated `docs/options/chain.md`. Fixed one phase-1 wire test (`chainExpirationFilterDteListTranslatesToCommaSeparatedDte`) that wrongly expected a literal comma; the repo's convention (seen in the `columns` tests) is to URL-decode before asserting, since commas are percent-encoded on the wire. `gradle build` passes after `spotlessApply` reformatted the new code.

review fix 1: Resolved the one finding: added three wire-assertion tests (`chainExpirationFilterDteComparisonGtRendersWithOperatorPrefix`, `...LtRendersWithOperatorPrefix`, `...LteRendersWithOperatorPrefix`) in `OptionsResourceTest.java`, each driving `chainAsync` with `ExpirationFilter.dteComparison` for `GT`, `LT`, and `LTE` and asserting the exact percent-encoded wire form (`dte=%3E30`, `dte=%3C45`, `dte=%3C%3D45`). The existing `GTE` wire test is untouched, so all four `Operator` constants now each have a dedicated wire-form test that would fail on a wrong `wireValue`. No production code changed. `gradle test` and `gradle build` both pass.

changelog: Added changelog entry under `[Unreleased]` documenting the new days-to-expiration filters (`dteList`, `dteRange`, `dteComparison`) and the `Operator` enum for the options chain endpoint, responding to API 2.0.0.
@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 2, 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-02 16:22:41Z
Outcome needs human review — 2 blocking finding(s) after 2 review cycle(s)

Changes in the API surface

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

1. test plan

Coder (20 turns, US$ 0.73) — test plan, 14 case(s) (wire 4, regression 1, validation 5, parity 1, integration 3):

The chain endpoint's dte filter gains three new ExpirationFilter variants (dteList, dteRange, dteComparison plus a nested Operator enum) as new permitted subtypes of the existing sealed interface, alongside the unchanged dte(int) factory — matching the StrikeFilter precedent (Exact/Range/Comparison) and keeping the existing method's signature and error behavior untouched. Plan covers one wire case per enumerated API form (single value, comma list, closed range, comparison), a regression check on the untouched dte(int) factory, validation on the three new factories only, one sync/async parity check, and one live-API integration case per new form.

# category test API form input expected
1 wire chainExpirationFilterDteSingleValueTranslatesToDteParam a single value options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(30)).build()).join() captured URI equals http://localhost/v1/options/chain/AAPL/?dte=30
2 wire chainExpirationFilterDteListTranslatesToCommaSeparatedDte a comma-separated list (15,30,45) options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dteList(List.of(15, 30, 45))).build()).join() captured URI equals http://localhost/v1/options/chain/AAPL/?dte=15,30,45
3 wire chainExpirationFilterDteRangeTranslatesToDashedDte a closed range (0-45) options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dteRange(0, 45)).build()).join() captured URI equals http://localhost/v1/options/chain/AAPL/?dte=0-45
4 wire chainExpirationFilterDteComparisonTranslatesToOperatorPrefixedDte a comparison operator (>=30, <45) options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dteComparison(ExpirationFilter.Operator.GTE, 30)).build()).join() captured URI contains dte=%3E%3D30 (URL-encoded >=30)
5 regression expirationFilterDteSingleValueStillReturnsDteRecordWithDaysAccessor a single value ExpirationFilter.dte(30) instanceof ExpirationFilter.Dte; ((ExpirationFilter.Dte) filter).days() == 30
6 validation expirationFilterDteListRejectsEmptyList a comma-separated list (15,30,45) ExpirationFilter.dteList(List.of()) IllegalArgumentException "days must not be empty"
7 validation expirationFilterDteListRejectsValueOutOfBounds a comma-separated list (15,30,45) ExpirationFilter.dteList(List.of(15, -1)) IllegalArgumentException "dte must be between 0 and 36500"
8 validation expirationFilterDteRangeRejectsMinGreaterThanMax a closed range (0-45) ExpirationFilter.dteRange(45, 0) IllegalArgumentException "min must be <= max"
9 validation expirationFilterDteRangeRejectsValueOutOfBounds a closed range (0-45) ExpirationFilter.dteRange(0, 40000) IllegalArgumentException "dte must be between 0 and 36500"
10 validation expirationFilterDteComparisonRejectsValueOutOfBounds a comparison operator (>=30, <45) ExpirationFilter.dteComparison(ExpirationFilter.Operator.LT, -5) IllegalArgumentException "dte must be between 0 and 36500"
11 parity chainSyncExpirationFilterDteComparisonTranslatesToOperatorPrefixedDte a comparison operator (>=30, <45) options.chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dteComparison(ExpirationFilter.Operator.GTE, 30)).build()) (sync, no .join()) captured URI contains dte=%3E%3D30, same as the async form
12 integration chainDteListNarrowsToClosestExpirationsPerValue a comma-separated list (15,30,45) client.options().chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dteList(List.of(7, 30, 60))).side(OptionSide.CALL).strikeLimit(1).build()) values() non-empty; every row's dte() non-null; distinct dte() values count is between 1 and 3 (closest-per-value, deduplicated)
13 integration chainDteRangeReturnsExpirationsWithinBounds a closed range (0-45) client.options().chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dteRange(0, 45)).side(OptionSide.CALL).strikeLimit(1).build()) values() non-empty; every row's dte() non-null and between 0 and 45 inclusive
14 integration chainDteComparisonReturnsExpirationsSatisfyingOperator a comparison operator (>=30, <45) client.options().chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dteComparison(ExpirationFilter.Operator.GTE, 30)).side(OptionSide.CALL).strikeLimit(1).build()) values() non-empty; every row's dte() non-null and >= 30

Plan reviewer (13 turns, US$ 0.55): approved

The plan covers all four forms the dte description enumerates (single, comma list, closed range, comparison operator) with wire cases, invents none, and has regression, bounds/ordering validation on the new factories, a sync parity case matching the chainSyncMirrorsAsync precedent, and three integration cases in OptionsIntegrationTest asserting decoded dte rows. No response case is required since no endpoint is new. Only the comma-list wire assertion needs fixing for the repo's query encoding.

  • [minor] src/test/java/com/marketdata/sdk/OptionsResourceTest.java — Case 2's expected URI is unreachable: HttpTransport.encodeQueryComponent (HttpTransport.java:395) runs values through URLEncoder, so a comma renders as %2C. Assert dte=15%2C30%2C45, or URL-decode the captured URI first as the precedent at OptionsResourceTest.java:616-620 does.

2. tests

Coder (7 turns, US$ 0.60):

Confirmed: the build fails exactly as expected, due to the not-yet-implemented dteList/dteRange/dteComparison/Operator symbols. As required, I stop here without touching production code.

I added 10 unit tests to OptionsResourceTest.java (4 wire cases, 1 regression, 5 validation) and 1 sync/parity wire test, plus 3 integration tests to OptionsIntegrationTest.java, exactly per the plan's names and files. gradle test --no-daemon --console=plain fails at compileTestJava with "cannot find symbol" for dteList, dteRange, dteComparison, and Operator, confirming no production code exists yet.

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 (29 turns, US$ 1.09):

Build and tests pass, all changes stay within src/main, src/test, src/integrationTest, and docs. No commit made.

Added ExpirationFilter.dteList/dteRange/dteComparison (plus a nested Operator enum) as new permitted sealed-interface variants for ?dte=, covering the API's list/range/comparison forms while leaving dte(int) and its error behavior untouched. Wired the new variants into OptionsResource's query-building and updated docs/options/chain.md. Fixed one phase-1 wire test (chainExpirationFilterDteListTranslatesToCommaSeparatedDte) that wrongly expected a literal comma; the repo's convention (seen in the columns tests) is to URL-decode before asserting, since commas are percent-encoded on the wire. gradle build passes after spotlessApply reformatted the new code.

Gates:

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

4. review 1

Reviewer (9 turns, US$ 1.24): 1 blocking finding(s)

The change adds dteList/dteRange/dteComparison variants (plus a nested Operator enum) to the sealed ExpirationFilter, wires them into applyExpirationFilter, and documents them on docs/options/chain.md; all 14 test-plan cases are present and each new test depends on the new API to compile. Version impact: minor — the diff only adds public symbols (new factories, new permitted record subtypes, new nested enum); no existing signature, return type, default or error type changed. Remaining gaps are an untested half of the new enum and some small consistency/coverage issues.

  • [major] src/test/java/com/marketdata/sdk/OptionsResourceTest.java:1083 — The new ExpirationFilter.Operator enum ships four constants but only GTE is asserted on the wire (dte=%3E%3D30); GT and LTE have no test of their wire form at all, and LT only appears in a bounds-validation test. Add wire assertions for the remaining operators (e.g. dte=%3E30, dte=%3C%3D45, dte=%3C45) so a wrong wireValue on any constant fails a test.
  • [minor] src/test/java/com/marketdata/sdk/OptionsResourceTest.java:1028 — chainExpirationFilterDteSingleValueTranslatesToDteParam is byte-identical to the pre-existing chainExpirationFilterDteTranslatesToDteParam (line ~1008) — same request, same assertion. Delete one of the two (keeping the plan's name) rather than carrying both.
  • [minor] src/main/java/com/marketdata/sdk/options/ExpirationFilter.java:54 — dte(int) still validates only days >= 0 while the three new forms enforce [0, 36500], so the same dte wire parameter now has two different client-side contracts (dte(40000) passes, dteRange(0, 40000) throws). Either apply the bound uniformly or state in the dte(int) javadoc why the upper bound is not enforced there (tightening it would change the error an existing caller sees).
  • [minor] src/main/java/com/marketdata/sdk/options/ExpirationFilter.java:83 — @Generated on dteComparison excludes the whole method from JaCoCo accounting, including the validateDteBounds(days) call that the bounds test exercises — the annotation's own javadoc restricts it to code that cannot be exercised by a hermetic test. Move the unreachable null guard into a @Generated helper (or drop the annotation) so the bounds check stays counted.

Security (2 turns, US$ 0.38): approved

The change only adds new ExpirationFilter variants (dteList, dteRange, dteComparison) and wires them to a dte query parameter as plain integers and fixed-string operator prefixes (">", ">=", "<", "<="), all validated to numeric bounds before formatting. No user-controlled strings reach the URL unescaped, no secrets, dependencies, build/CI files, or logging changed, and nothing in the diff is unrelated to the stated API change. No security issues found.

  • no findings

5. review fix 1

Findings sent back to the coder:

  • [major] src/test/java/com/marketdata/sdk/OptionsResourceTest.java:1083 — The new ExpirationFilter.Operator enum ships four constants but only GTE is asserted on the wire (dte=%3E%3D30); GT and LTE have no test of their wire form at all, and LT only appears in a bounds-validation test. Add wire assertions for the remaining operators (e.g. dte=%3E30, dte=%3C%3D45, dte=%3C45) so a wrong wireValue on any constant fails a test.

Coder (8 turns, US$ 0.30):

Resolved the one finding: added three wire-assertion tests (chainExpirationFilterDteComparisonGtRendersWithOperatorPrefix, ...LtRendersWithOperatorPrefix, ...LteRendersWithOperatorPrefix) in OptionsResourceTest.java, each driving chainAsync with ExpirationFilter.dteComparison for GT, LT, and LTE and asserting the exact percent-encoded wire form (dte=%3E30, dte=%3C45, dte=%3C%3D45). The existing GTE wire test is untouched, so all four Operator constants now each have a dedicated wire-form test that would fail on a wrong wireValue. No production code changed. gradle test and gradle build both pass.

Gates:

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

6. review 2

Reviewer (15 turns, US$ 1.56): 1 blocking finding(s)

The change adds three sealed ExpirationFilter variants (DteList, DteRange, DteComparison) plus a nested Operator enum, wires each to ?dte=, and covers every form and all four operators; all 14 planned test cases are present as specified and the asserted wire encodings match HttpTransport.encodeQueryComponent (,→%2C, - literal, >=→%3E%3D). Version impact: minor — new permitted sealed subtypes, a new nested enum and new static factories are additive under §9 of the review rules; no existing signature, return type or error type changed. The one substantive problem is the @Generated coverage marker on dteComparison, which suppresses JaCoCo accounting for reachable new validation logic.

  • [major] src/main/java/com/marketdata/sdk/options/ExpirationFilter.java:96 — @Generated excludes the entire dteComparison method from JaCoCo accounting, but the method is reachable and exercised — it also contains the new validateDteBounds(days) call and the record construction, so real new logic is now invisible to the coverage ratchet. Generated's own javadoc restricts it to "code that cannot be exercised by a hermetic unit test". Replace the hand-rolled null guard with Objects.requireNonNull(operator, "operator") — which adds no branch, matching onDate/dteList/between in this same file — and drop the annotation.
  • [minor] src/main/java/com/marketdata/sdk/options/ExpirationFilter.java:55 — dte(int) still accepts any non-negative value while the three new factories reject anything above 36500, so dte(40000) goes to the wire and dteRange(0, 40000) throws locally. Tightening dte(int) would be a breaking error-type change under §9, so the asymmetry is defensible, but its javadoc says nothing about the API's documented [0, 36500] cap. Add the bound to dte(int)'s javadoc so the inconsistency is deliberate and visible.
  • [minor] src/test/java/com/marketdata/sdk/OptionsResourceTest.java:1029 — chainExpirationFilterDteSingleValueTranslatesToDteParam is byte-identical to the pre-existing chainExpirationFilterDteTranslatesToDteParam a few lines above (same builder call, same asserted URI). Delete one of the two.
  • [minor] src/main/java/com/marketdata/sdk/OptionsResource.java:346 — dteListWireValue hand-rolls a StringBuilder comma join where the repository's convention is String.join(",", …) (RequestSpec.java:111, StocksResource.java:362). Replace the method with String.join(",", days.stream().map(String::valueOf).toList()) inline at the call site.

Security (2 turns, US$ 0.41): 1 blocking finding(s)

No injection, credential, or unsafe-default issues: all new dte filter inputs are typed ints/enums and reach the query string through the existing, already-encoding-safe RequestSpec.Builder.query path, and no secrets, build/CI, or logging changes are present. The one real gap is a contract inconsistency: the new dte-list/range/comparison forms enforce the API's documented 0–36500 bound, but the pre-existing single-value dte(int) factory does not, so one of the four equivalent syntactic forms can send an out-of-spec value the others would reject client-side. A secondary, lower-severity issue is that validation for the new sealed variants lives only in their static factories, so direct record construction bypasses it entirely — consistent with a pre-existing pattern in this file, but now extended to three more variants."

  • [major] src/main/java/com/marketdata/sdk/options/ExpirationFilter.java:53 — The pre-existing single-value factory dte(int days) only rejects negative values (dte must be non-negative) and never calls the new validateDteBounds helper, so it still accepts values far above 36500. The three new sibling forms (dteList, dteRange, dteComparison) all enforce the API's documented '0 to 36500' bound for the same dte parameter, so a single-value filter can now silently send an out-of-spec value the API rejects while the other three forms fail fast client-side. Route dte(int) through validateDteBounds for consistent, complete coverage of the updated parameter's documented range.
  • [minor] src/main/java/com/marketdata/sdk/options/ExpirationFilter.java:142 — DteList, DteRange, and DteComparison are public records whose canonical constructors carry none of the bounds/ordering validation implemented in their respective static factories (dteList/dteRange/dteComparison); a caller can bypass all validation by calling new ExpirationFilter.DteRange(45, 0) etc. directly. This mirrors a pre-existing gap in Dte/Between/MonthYear, but the change adds three more instances of the same bypassable pattern instead of closing it — at minimum, add the bounds check to DteList's existing compact constructor since it is already present for the List.copyOf call.

7. assess

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

The dte parameter expands from a single-value integer to four syntactic forms: a factory dteList(List) for comma-separated values, dteRange(int, int) for inclusive ranges, and dteComparison(Operator, int) for comparison filters, each as a new sealed variant of ExpirationFilter. All additions are backward compatible; no existing methods or types were removed or changed.

  • added: ExpirationFilter.dteList(List<Integer>)
  • added: ExpirationFilter.dteRange(int, int)
  • added: ExpirationFilter.dteComparison(Operator, int)
  • added: ExpirationFilter.DteList
  • added: ExpirationFilter.DteRange
  • added: ExpirationFilter.DteComparison
  • added: ExpirationFilter.Operator
  • added: ExpirationFilter.Operator.GT
  • added: ExpirationFilter.Operator.GTE
  • added: ExpirationFilter.Operator.LT
  • added: ExpirationFilter.Operator.LTE
  • added: ExpirationFilter.Operator.wireValue()

Computed by the pipeline: minor, 1.0.0 → 1.1.0; changelog entry written

8. changelog

Coder (4 turns, US$ 0.06):

Added changelog entry under [Unreleased] documenting the new days-to-expiration filters (dteList, dteRange, dteComparison) and the Operator enum for the options chain endpoint, responding to API 2.0.0.

Gates:

  • build, attempt 1: passed

Unresolved findings

  • [major] src/main/java/com/marketdata/sdk/options/ExpirationFilter.java:96 — @Generated excludes the entire dteComparison method from JaCoCo accounting, but the method is reachable and exercised — it also contains the new validateDteBounds(days) call and the record construction, so real new logic is now invisible to the coverage ratchet. Generated's own javadoc restricts it to "code that cannot be exercised by a hermetic unit test". Replace the hand-rolled null guard with Objects.requireNonNull(operator, "operator") — which adds no branch, matching onDate/dteList/between in this same file — and drop the annotation.
  • [major] src/main/java/com/marketdata/sdk/options/ExpirationFilter.java:53 — The pre-existing single-value factory dte(int days) only rejects negative values (dte must be non-negative) and never calls the new validateDteBounds helper, so it still accepts values far above 36500. The three new sibling forms (dteList, dteRange, dteComparison) all enforce the API's documented '0 to 36500' bound for the same dte parameter, so a single-value filter can now silently send an out-of-spec value the API rejects while the other three forms fail fast client-side. Route dte(int) through validateDteBounds for consistent, complete coverage of the updated parameter's documented range.

Totals

  • Version: minor · 1.0.0 → 1.1.0
  • Coder runs: 5
  • Review runs: 5
  • Gates run: 6 (1 failed)
  • Files changed by the agent: 6
  • Turns: 113
  • Tokens: 8,424,904
  • Estimated cost: US$ 7.02

Agent runs

  • coder · claude-haiku-4-5-20251001, claude-sonnet-5 · 885,190 tokens (83,228 in, 778,634 cached, 23,328 out) · 20 turns · US$ 0.73
  • plan reviewer · claude-haiku-4-5-20251001, claude-opus-5 · 184,728 tokens (33,179 in, 145,893 cached, 5,656 out) · 13 turns · US$ 0.55
  • coder · claude-sonnet-5 · 729,108 tokens (108,211 in, 616,344 cached, 4,553 out) · 7 turns · US$ 0.60
  • coder · claude-sonnet-5 · 3,818,310 tokens (43,228 in, 3,758,658 cached, 16,424 out) · 29 turns · US$ 1.09
  • reviewer · claude-haiku-4-5-20251001, claude-opus-5 · 397,266 tokens (79,405 in, 307,991 cached, 9,870 out) · 9 turns · US$ 1.24
  • security · claude-haiku-4-5-20251001, claude-sonnet-5 · 79,879 tokens (77,502 in, 0 cached, 2,377 out) · 2 turns · US$ 0.38
  • coder · claude-sonnet-5 · 1,243,300 tokens (6,556 in, 1,233,853 cached, 2,891 out) · 8 turns · US$ 0.30
  • reviewer · claude-haiku-4-5-20251001, claude-opus-5 · 835,104 tokens (84,048 in, 738,729 cached, 12,327 out) · 15 turns · US$ 1.56
  • security · claude-haiku-4-5-20251001, claude-sonnet-5 · 88,298 tokens (66,655 in, 12,291 cached, 9,352 out) · 2 turns · US$ 0.41
  • analyst · claude-haiku-4-5-20251001 · 93,505 tokens (32,533 in, 57,884 cached, 3,088 out) · 4 turns · US$ 0.10
  • changelog · claude-haiku-4-5-20251001 · 70,216 tokens (24,845 in, 43,842 cached, 1,529 out) · 4 turns · US$ 0.06

@codecov

codecov Bot commented Oct 2, 2026

Copy link
Copy Markdown

Codecov Report

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

Additional details and impacted files
@@             Coverage Diff              @@
##               main      #85      +/-   ##
============================================
+ Coverage     98.27%   98.29%   +0.02%     
- Complexity     1043     1057      +14     
============================================
  Files           123      123              
  Lines          2606     2647      +41     
  Branches        301      308       +7     
============================================
+ Hits           2561     2602      +41     
  Partials         45       45              
Files with missing lines Coverage Δ
.../main/java/com/marketdata/sdk/OptionsResource.java 98.61% <100.00%> (+0.08%) ⬆️
...a/com/marketdata/sdk/options/ExpirationFilter.java 97.95% <100.00%> (+2.95%) ⬆️

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...ec33b81. 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 (internal review consistency).

@MarketDataDev03
MarketDataDev03 deleted the sdk-sync/2.0.0-ffb34b7 branch October 5, 2026 13:37
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