basecamp / basecamp/basecamp-sdk
Conformance fixtures have no clock, so SPEC §6's positive HTTP-date branch is unfalsifiable
- Dominant language
- Go
- Stars
- 49
- Forks
- 12
- Avg merge
- 20h 47m
- Merged PRs (30d)
- 89
Description
Fell out of #564. SPEC §6 step 2 says a `Retry-After` HTTP-date becomes `max(0, date - now())` seconds
and is honoured when that is **positive**. Conformance can pin the negative half of that rule — a past
date must fall through to backoff — and cannot pin the positive half at all, because a fixture is a
static JSON literal and the harness has no clock.
The bind is exact and has no fixture-level escape:
- A date far enough in the future to stay future-dated for the life of the repo makes every compliant
SDK sleep for years. `Retry-After` is explicitly exempt from SPEC §7's backoff ceiling, so nothing
clamps it.
- A date close enough to assert a small `delayBetweenRequests` against is past-dated within seconds of
being written, at which point the case silently reverts to testing the fall-through path.
So "compliant" and "non-compliant" are only distinguishable for a date a few seconds ahead of the
run's own `now()`, and no runner can produce one. #564 therefore pins the positive half in per-SDK
unit tests and says so in the fixture's `description`, which is the honest shape but leaves the one
branch that actually regressed (Kotlin's, ignored for the SDK's whole life) without a cross-SDK guard.
### What a fix looks like
A substitution token in `mockResponses[].headers` values, resolved **at response-serving time** to an
IMF-fixdate N seconds ahead — e.g. `"Retry-After": "{{httpdate+3s}}"` — plus a fixture asserting
`delayBetweenRequests` with a `min` between the backoff term (1000 ms) and the guaranteed floor
(~2000 ms for the SDKs that truncate the difference to whole seconds; Go and Ruby do, Swift and
TypeScript round up).
Five of the six runners can take this as a transform at the point where they copy the fixture's header
map into the response:
| Runner | Site |
|---|---|
| Go | `conformance/runner/go/main.go:403-410` |
| Python | `conformance/runner/python/runner.py:1090-1092` |
| Swift | `conformance/runner/swift/Sources/ConformanceRunner/ScriptedTransport.swift:121-124` |
| TypeScript | `conformance/runner/typescript/runner.test.ts:1159-1166` |
| Kotlin | `kotlin/conformance/src/main/kotlin/com/basecamp/sdk/conformance/Main.kt:478-483` |
Ruby needs one extra step: it builds the entire response queue eagerly, headers included, *before* any
request arrives (`conformance/runner/ruby/runner.rb:1136-1145`), so the header map has to move into the
`to_return` block at `runner.rb:1158` first — otherwise the substitution resolves against the wrong
`now`.
`conformance/schema.json:95-101` types header values as plain strings today and would need to document
the token. The repo's convention would also want the resolver unit-tested per runner, the way
`checkDelayGaps` is.
### Why it was not done in #564
It is a six-runner change to the harness inside a two-SDK parsing fix, and `make check` runs all six
conformance suites, so it carries its own verification surface. Worth doing deliberately, on its own,
rather than as a rider — but it should be done, because "the fixture cannot fail" is the reason #564's
Kotlin defect survived a green suite in the first place, and the positive-date branch is still in
exactly that position.
Contributor guide
Research direction
Start with the runner sites listed in the issue, then inspect conformance/schema.json and the existing checkDelayGaps resolver tests. Implement and test response-time HTTP-date substitution across all six runners, including Ruby's to_return path, and add a fixture that asserts the positive Retry-After delay with make check.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, kotlin, python, ruby, swift, typescript
- Domain
- testing-qa, tooling
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100