Skip to content

feat: RETRY-conformance contract tests for FDv1 streaming and polling - #401

Closed
tanderson-ld wants to merge 6 commits into
v3from
ta/sdk-2808-retry-conformance-tests
Closed

feat: RETRY-conformance contract tests for FDv1 streaming and polling#401
tanderson-ld wants to merge 6 commits into
v3from
ta/sdk-2808-retry-conformance-tests

Conversation

@tanderson-ld

Copy link
Copy Markdown
Contributor

Summary

Adds contract test coverage for the RETRY specification's non-permanent-stop guarantee, applied to FDv1 streaming and polling data sources. Every SDK that adopts RETRY-conforming behavior will opt into these tests via the new capabilities; SDKs that have not yet adopted keep passing the existing legacy tests, unmodified in behavior.

Draft status: this PR can land test scaffolding, capabilities, and the tests themselves — but no SDK reports the new capabilities yet, so the new tests all skip today. The Go server SDK (reference implementation per the parent epic) will be the first SDK to opt in; that work happens separately.

Design document

The implementation follows the plan at individual/tanderson/tickets/SDK-2808/implementation/plan.md in sdk-scratchpad.

Structure — 6 commits

  1. refactor: rename unrecoverableErrors to unexpectedErrors — terminology alignment with the RETRY specification. No behavior change.
  2. feat: add retry-conformance-fdv1-{streaming,polling} capabilities — two new capabilities in servicedef/service_params.go, following the CapabilityFDv1Fallback pattern.
  3. refactor: guard legacy "do not retry" tests behind capability absence — old permanent-stop tests now skip on SDKs that declare the RETRY-conformance capability. No SDK declares it today, so no test coverage changes.
  4. feat: add ExtendedInitialDelayMS and ResetThresholdMS servicedef fields — test-mode timing knobs analogous to the existing InitialRetryDelayMS. Streaming gets both; polling gets only ExtendedInitialDelayMS (polling's reset is count-based).
  5. test: server-side streaming retry-conformance scenarios — 4 new subtests: retry after 401/403/405 on initial connect, retry on reconnect, extended-regime engagement (paired-bound assertion), and sustained-failure no-permanent-stop.
  6. test: server-side polling retry-conformance scenarios — 1 new subtest (functional recovery after 401/403/405), marked LongRunning because polling's PollInterval minimum forces 30 s+ per iteration.

What's NOT in this PR

  • v2 port — will follow as a separate PR after this one merges (paired-PR pattern used elsewhere in this repo for FDv1-relevant test additions). Sequential to avoid the v2 port becoming a moving target during this PR's review.
  • Client-side polling (mobile foreground / background cadence, browser JS / PHP single-shot) — separate ticket, different test shape.
  • TLS / certificate validation failure scenarios — separate ticket, requires TLS-specific mock infrastructure.
  • Additional polling scenarios — extended-regime shape verification, wait-floor verification, reset after 2 consecutive successful polls. All feasible but each requires careful attention to LongRunning timing budgets. Deferred to follow-up.

Capability gating

Each new test skips unless the SDK declares the corresponding capability:

  • retry-conformance-fdv1-streaming — SDK's FDv1 streaming data source is RETRY-conformant AND honors ExtendedInitialDelayMS / ResetThresholdMS on SDKConfigStreamingParams.
  • retry-conformance-fdv1-polling — SDK's FDv1 polling data source is RETRY-conformant AND honors ExtendedInitialDelayMS on SDKConfigPollingParams.

Legacy "do not retry after unexpected HTTP error" tests skip when the corresponding capability IS present (mirror gating). Once every SDK reports the capability, both the gate and the legacy tests can be decommissioned.

Validation

  • go build ./... — clean.
  • go vet ./... — clean.
  • go test -c ./sdktests -o /dev/null — clean.
  • Runtime validation happens when the first SDK opts into the capability. That's not in this PR.

Test plan

  • Reviewer sanity-checks the capability naming and gating pattern.
  • Reviewer confirms the timing pattern for the streaming paired-bound assertion is safe against CI variance.
  • Reviewer confirms the LongRunning tagging on the polling test is the right pattern (or suggests an alternative).
  • Once a RETRY-conformant SDK exists, run the new tests against it to validate the assertions exercise real behavior.

Aligns terminology with the RETRY specification's `unexpected` failure
classification. `recoverableErrors` stays as-is (the name is still
accurate; SDKs do recover from these). Test names also updated to
"do not retry after unexpected HTTP error" for consistency.

No behavioral change. The tests still assert legacy permanent-stop
behavior; subsequent commits will gate them behind absence of the
retry-conformance capability.
Declares two new capabilities in servicedef/service_params.go, following
the CapabilityFDv1Fallback pattern:

- retry-conformance-fdv1-streaming: SDK's FDv1 streaming data source
  conforms to the RETRY specification (no permanent stops; extended-
  regime backoff on unexpected errors; TLS/cert failures classified as
  unexpected).
- retry-conformance-fdv1-polling: same for the FDv1 polling data source,
  additionally with the PollInterval wait floor.

Capabilities are scoped per data source so an SDK can partially adopt.
An SDK declaring either capability must also honor the corresponding
extended-regime timing knobs on the streaming / polling params (added
in a later commit). Capability presence gates the new retry-conformance
tests; capability absence gates the legacy permanent-stop tests.
The two "do not retry after unexpected HTTP error" tests describe
legacy permanent-stop behavior that is being retired by the RETRY
specification. Under RETRY, 401 / 403 / other-4xx responses trigger an
extended-regime backoff rather than a permanent stop.

These tests are now guarded so they run only when the SDK does NOT
declare the retry-conformance-fdv1-streaming capability. SDKs that
declare the capability will run the new "retry after unexpected HTTP
error" tests instead (added in a later commit). Once every SDK reports
the capability, both the gate and the legacy tests can be removed.

No behavioral change for SDKs today: none report the new capability,
so all continue to run the legacy tests.
Adds test-mode timing knobs on SDKConfigStreamingParams and
SDKConfigPollingParams, analogous to the existing InitialRetryDelayMS.
These allow retry-conformance tests to compress the RETRY specification's
extended regime (default 5 min -> 1 hour) into an observable window.

Streaming params get both knobs:
- ExtendedInitialDelayMS: initial delay of the extended regime.
- ResetThresholdMS: reset threshold returning to the normal regime.

Polling params get only ExtendedInitialDelayMS; polling's reset is
count-based (2 consecutive successful poll responses per the polling
spec), not time-based.

An SDK declaring CapabilityRetryConformanceFDv1{Streaming,Polling} MUST
honor the corresponding fields. SDKs that don't declare the capability
can ignore the fields; the new fields silently pass through JSON
deserialization.
Adds four new subtests to doServerSideStreamRetryTests, all guarded by
CapabilityRetryConformanceFDv1Streaming being present. They assert
RETRY-conformant behavior for unexpected HTTP errors (401 / 403 / other
4xx):

- retry after unexpected HTTP error on initial connect: sequence of two
  401/403/405 followed by a successful stream open; SDK reaches ready
  state and evaluates against the successful stream's data.
- retry after unexpected HTTP error on reconnect: initial stream, then
  two errors mid-stream, then a successful second stream; SDK evaluates
  against the second stream's data.
- enters extended-regime backoff after unexpected HTTP error:
  paired-bound assertion: after a 401, SDK does NOT reconnect within
  the normal-regime window (100 ms) AND DOES reconnect within the
  extended-regime window (3 s). Proves the SDK's timing is genuinely
  extended-regime, not normal-regime and not permanent stop.
- does not permanently stop under sustained unexpected HTTP errors:
  endpoint returns 401 forever; SDK still making connection attempts
  after several observations.

Supporting infrastructure:
- retryConformanceStreamConfig helper sets InitialRetryDelayMS = 1 ms
  and ExtendedInitialDelayMS = 500 ms.
- compressedExtendedInitialDelay constant documents the choice of
  500 ms as a discriminating window.
- extendedRegimeConnectionTimeout (3 s) generously bounds the wait for
  an extended-regime reconnect.

These tests skip against every SDK that does not report the capability;
they exercise real behavior only against SDKs that have implemented
RETRY-conforming streaming.
Adds a new file sdktests/server_side_poll_retry.go with a first-pass
polling retry-conformance test. Guarded by
CapabilityRetryConformanceFDv1Polling.

Marked LongRunning because server-side polling enforces a PollInterval
minimum of 30 seconds; the extended-regime effective initial delay is
max(ExtendedInitialDelayMS, PollInterval), so tests take ~30-60 seconds
per iteration. Follows the same pattern as PollingIntervalTests.

This first pass covers only the core "no permanent stop" guarantee:
after an unexpected HTTP error (401 / 403 / 405), the SDK makes another
poll request and eventually evaluates against the successful poll's data.

Additional polling scenarios (extended-regime shape, wait-floor
verification, reset after 2 consecutive successful polls) are deferred
to follow-up work; they require careful attention to the LongRunning
timing budget and possibly SDK-side changes to bypass the PollInterval
minimum for test-mode.

Wired into doServerSidePollTests via a new "retry behavior" subtest.
@tanderson-ld tanderson-ld changed the title SDK-2808: Add RETRY-conformance contract tests for FDv1 streaming and polling feat: RETRY-conformance contract tests for FDv1 streaming and polling Aug 3, 2026
@tanderson-ld

Copy link
Copy Markdown
Contributor Author

Superseded by #404, which targets v2 and drives the FDv1 streaming/polling paths — the correct scope for the initial RETRY-conformance work. This PR was built against v3 and would have exercised FDv2 code paths, which are out of scope for the FDv1-only conformance tests.

tanderson-ld added a commit that referenced this pull request Aug 19, 2026
…ling (#404)

## Summary

Adds contract tests exercising the RETRY specification's
non-permanent-stop guarantee for the FDv1 streaming and polling data
sources. Introduces two capability gates so SDKs can adopt
independently:

- `retry-conformance-fdv1-streaming`
- `retry-conformance-fdv1-polling`

The new tests exercise the SDK's real production 5-minute
extended-regime timing — no test-harness bypass, no compression knobs on
the servicedef, no test-only surface in the SDK's public API. They are
marked as long-running (`t.LongRunning()`) and only run when the harness
is invoked with `-enable-long-running-tests`.

Legacy "do not retry after unrecoverable HTTP error" scenarios remain,
gated behind the ABSENCE of the streaming capability so SDKs can migrate
on their own schedule. Once every SDK reports the new capabilities, both
the legacy tests and the gates can be removed.

## What's added

**Streaming (guarded by `retry-conformance-fdv1-streaming` +
`-enable-long-running-tests`):**
- retry after unexpected HTTP error on initial connect (401/403/405)
- retry after unexpected HTTP error on reconnect (401/403/405)
- enters extended-regime backoff after unexpected HTTP error
(paired-bound timing assertion)
- does not permanently stop under sustained unexpected HTTP errors
- returns to normal-regime backoff after healthy operation (streaming
spec's activeSince reset: 75 s silent stream after an extended-regime
reconnect, then next reconnect must be at normal-regime timing)

**Polling (guarded by `retry-conformance-fdv1-polling` +
`-enable-long-running-tests`):**
- retry after unexpected HTTP error on initial poll (401/403/405)
- returns to normal-regime cadence after two consecutive successful
polls (RETRY §1.8.1 count-based reset)

## Design: long-running instead of compressed timings

An earlier revision of this PR exposed extended-regime timing knobs on
the servicedef (`ExtendedInitialDelayMS`, `ResetThresholdMS`) so tests
could compress the 5-minute extended-initial delay into a ~30-second
observable window. Reviewers pushed back because that shape forced SDKs
to expose corresponding test-only knobs on their public API surface just
to be testable by the harness.

This revision drops all compression knobs and exercises real production
timing instead. That makes each retry-conformance subtest take several
minutes of wall clock, which is why the tests are marked
`t.LongRunning()` and gated behind `-enable-long-running-tests`. Per-SDK
CI schedules for the long-running suite are out of scope for this PR
(planned follow-up work).

Framework support for `t.LongRunning()` and the
`-enable-long-running-tests` flag was ported from v3 in #428, a
prerequisite that landed before this PR's refactor.

## Relationship to PR #401

Supersedes #401, which targeted `v3` and exercised FDv2 code paths — out
of scope for the initial RETRY-conformance work, which is FDv1-only.
This PR targets `v2` and drives the FDv1 streaming/polling paths in the
SDK testservices. #401 will be closed with a pointer here.

SDK-2808

## Test plan
- [x] Contract tests pass against go-server-sdk PR #429 (the reference
server SDK implementation) at real production 5-minute extended-regime
timing (~3-hour run with `-enable-long-running-tests`)
- [x] Legacy "do not retry" tests still pass for SDKs without the new
capability (verified by SDK capability check)
- [x] Retry-conformance tests are correctly SKIPPED for SDKs without the
capability
- [x] Retry-conformance tests are correctly SKIPPED when
`-enable-long-running-tests` is not set
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant