docs(sdk): contract 1.8 — retry policy, decision memo, close(), telemetry (D5) - #283
Merged
Merged
Conversation
…etry (D5) Adds the four SDK quality-of-life sections D5 calls for, and fixes a dangling normative reference found while writing the first of them. §16 Retry Policy (MUST). §11.2 rule 5 and §14.2 rule 6 have both been instructing SDKs to retry "under the SDK's existing bounded read-only retry policy" — a policy that was never defined anywhere in this contract. Surveying the eleven repos: Java had one (3 attempts, 200 ms base, 5 s cap, full jitter, Retry-After honored), Rust had a different one (3 attempts, library-default backoff, no jitter, no Retry-After), and the other nine had none at all — only §9's refresh-then-retry-once, which is a different mechanism. So two normative clauses were deferring to eleven separate guesses. §16 is that missing policy, as one binding table. The values follow Java's, which was the closest to what the phrase implied. Full jitter is specified explicitly and tested for its full [0, backoff] range, because partial jitter keeps every client's retries clustered and so causes the failure mode retries are meant to fix. Retry-After is a floor, never a ceiling, so Retry-After: 0 cannot defeat the backoff. Two things §16 is careful about: - Eligibility is "changes no server state", NOT "is an HTTP GET". AXIAM's authz check is a POST with a body and is the single most important operation in the section; an SDK gating on the verb retries nothing that matters. The ineligible list is justified twice over — state change, and single-use credentials that a retry replays into a hard invalid_grant. - It does not amend §9. The two compose in one direction only: an operation inside a §9 refresh-retry may itself be §16-retried, but a §9 refresh may not be, and a mid-operation refresh does not reset the §16 budget. §17 Client-Side Decision Memo (MAY, off by default). §11.2 rule 6's ban on decision caching stands as the default; §17 is the single opt-in exception, mirroring the server's own decision-cache and session-validation-cache bound rather than inventing a second staleness story. Unlike the server's equivalent setting — an unclamped u64 that lets an operator configure a multi-hour staleness window — the client TTL is clamped to 5 s. Allows and denies cache identically, because asymmetric caching leaks the outcome through latency. Read-your-own-writes is explicitly not guaranteed, in those words, because the admin UI that grants a role and immediately re-checks is the case that breaks and it breaks silently. Rule 10 forbids the memo from serving §11.2 rule 5's fail-closed path, which would invert fail-closed into fail-open exactly when it matters. §18 Deterministic Shutdown (MUST). Every SDK exposes shutdown in its own language's existing idiom rather than a new invented spelling. Idempotent, releases everything, use-after-close raises rather than being undefined. Close MUST NOT reach the network: an SDK that logged out on close would silently end sessions on every deploy, and the required tests assert against the transport to catch exactly that. §19 Telemetry Hooks (SHOULD). Optional callbacks so callers can wire metrics without this SDK depending on OpenTelemetry; the OTel adapter is an examples/ entry per SDK. A hook that throws is swallowed — telemetry may not fail an authorization check. Payloads carry a fixed field list and never tokens, bodies, or Sensitive<T> contents, since this surface exists to be shipped to a metrics backend. Path templates, not substituted URLs, so a metric label cannot become a cardinality bomb. One request_start/request_end pair per attempt, not per logical call, so a caller can count real wire calls — and §16.5 requires the retry event precisely because a retried-then-succeeded operation is otherwise invisible, which is the standing objection to automatic retry. Conformance. §16 and §18 are MUST and land unimplemented everywhere (§18 existed only in the TS/Python gRPC clients and C's axiam_client_free). The Closing Notes say so plainly: each SDK is non-conformant on those two until the D6 fan-out lands them, and its README must not imply otherwise. §17 and §19 are optional and named in the statement when shipped, like §14/§15. Contract 1.6 → 1.7 was the device_login errata; this is 1.7 → 1.8. Docs only — no code, no OpenAPI, no proto. Verified: check-doc-links (129 links, 21 files), check-remediation-evidence (37 verified, 0 failed), check-frontend-coverage, and the OpenAPI parse gate all pass.
This was referenced Aug 9, 2026
feat: contract 1.8 — §16 retry, §17 memo, §18 close(), §19 telemetry (D5)
ilpanich/axiam-rust-sdk#45
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the four SDK quality-of-life sections D5 calls for — and fixes a dangling normative reference found while writing the first of them.
Docs only: one file,
sdks/CONTRACT.md, +344/−7. No code, no OpenAPI, no proto. This is the contract half of D5; the eleven-repo implementation fan-out follows under D6's recurring gate.The defect this uncovered
§11.2 rule 5 and §14.2 rule 6 have both been instructing SDKs to retry "under the SDK's existing bounded read-only retry policy" — and no such policy was ever defined in this contract.
I surveyed all eleven repos before writing a line of it:
Retry-Afterbackondefaults,NetworkErroronly, no jitter, noRetry-AfterSo two normative clauses were deferring to eleven separate guesses, and the two SDKs that did implement something disagreed with each other. Same defect class as the §14.3
device_loginerrata in 1.7, but this one was live in two sections at once.§16 is that missing policy as one binding table. The values follow Java's, which was closest to what the phrase implied.
What the four sections settle
§16 Retry Policy — MUST
POSTwith a request body and is the single most important operation in the section. An SDK that gates retry on the HTTP verb retries nothing that matters. Called out in bold because it is the mistake this section exists to prevent.0and to1to prove the range really is[0, backoff].Retry-Afteris a floor, never a ceiling — so aRetry-After: 0cannot defeat the backoff.invalid_grant.§17 Client-Side Decision Memo — MAY, off by default
§11.2 rule 6's ban on decision caching stands as the default; §17 is the single opt-in exception, and the rule is amended to say exactly that rather than being quietly contradicted.
DECISION_CACHE_TTL_SECS/SESSION_VALIDATION_CACHE_TTL_SECSbound rather than inventing a second staleness story.u64that lets an operator configure a multi-hour staleness window — a known residual. The client has no reason to repeat it, and the contract says so.§18 Deterministic Shutdown — MUST
Each language's existing idiom, not a new invented spelling. Idempotent; releases everything; use-after-close raises rather than being undefined behaviour in the manual-memory languages.
Close must not reach the network. An SDK that logged out on close would silently end sessions on every deploy. The required test asserts against the transport rather than the return value, because that is what catches a
logoutaccidentally wired in.§19 Telemetry Hooks — SHOULD
Optional callbacks so callers can wire OpenTelemetry or Prometheus without this SDK depending on either; the OTel adapter ships as an
examples/entry per SDK.Sensitive<T>contents — this surface exists to be shipped to a metrics backend, which is the last place a bearer token should land. The required test scans the serialized payload for the fixture token, the same discipline §12/§14/§15 use on error paths.request_start/request_endpair per attempt, not per logical call, so a caller can count real wire calls. §16.5 requires theretryevent for the same reason: a retried-then-succeeded operation is otherwise invisible, and that silence is the standing objection to automatic retry.Conformance — stated plainly, not papered over
§16 and §18 are MUST-level and land unimplemented everywhere. §18 exists today only in the TypeScript and Python gRPC clients and C's
axiam_client_free; §16 exists in two disagreeing forms.The Closing Notes say so directly: each SDK is non-conformant on those two sections until the D6 fan-out lands them, and its README must not imply otherwise. They are not named in the conformance statement, because a MUST is not something an SDK opts into. §17 and §19 are optional and are named when shipped, like §14/§15.
Verification
scripts/check-doc-links.shscripts/check-remediation-evidence.pyscripts/check-frontend-coverage.pydocs/api/openapi.jsonparsesNew internal anchors follow the file's existing convention and resolve under the link-checker.
Notes
1.6 → 1.7was thedevice_loginerrata (docs(sdk): contract 1.7 — device_login credential-adoption errata (D6) #282). This is1.7 → 1.8.Generated by Claude Code