feat(spec): v0.1.25.16 — remaining_ttl_ms on reservation responses + normative heartbeat algorithm - #148
Conversation
extend_by_ms is relative to the reservation's CURRENT expires_at_ms, so a keep-alive client that extends by ttl_ms on every sub-TTL beat (every ttl/2) drifts expiry outward by ttl/2 per beat — an effectively unbounded zombie-reservation window that works against the grace_period_ms zombie-reservation rationale — and burns any server-side extension budget (MAX_EXTENSIONS_EXCEEDED) twice as fast as necessary. All four official SDKs independently wrote this bug; the spec now warns the next implementer. - Add HEARTBEAT GUIDANCE block to the extendReservation operation: keep-alive clients SHOULD keep remaining lifetime within roughly [ttl/2, 1.5x ttl] (extend by ttl_ms on every second beat of a ttl/2 cadence, or extend by the beat interval per beat); servers MAY clamp effective new expiry to a policy maximum lead. - Add one-line pointer in ReservationExtendRequest.extend_by_ms. - Description text only; no endpoint, schema, or wire-format change. semantic_base remains 0.1.25. - Bump info.version 0.1.25.15 -> 0.1.25.16; changelog entry; spec-index pins (spec_index 0.1.43 -> 0.1.44); regenerate merged artifacts.
Adversarial review of the v0.1.25.16 HEARTBEAT GUIDANCE found the recommended alternate-beat pattern has a single-failure lead-0 race (steady-state attempts occur at exactly ttl/2 lead; one failed extend puts the retry at the expiry instant), the [ttl/2, 1.5xttl] band held only on the success path and was silently breached by failed beats and by the server clamping the same paragraph authorizes, and clients were never told to use the authoritative returned expires_at_ms. Rewrite the guidance to recommend a clock-skew-free lead estimate from the returned expires_at_ms (server-frame differences + client-monotonic elapsed time), beat at ~ttl/2, extend by ttl_ms when lead < 1.5xttl_ms and skip otherwise; warn against unconditional extend-by-ttl and blind alternate-beat without lead tracking; note heartbeat stop conditions and idempotency_key reuse on retry. Same unreleased 0.1.25.16 change; description text only, no schema change. Changelog entry updated and merged artifacts regenerated.
|
Guidance upgraded from alternate-beat to a lead-estimate pattern (commit c52b71b) after adversarial review found failure-path hazards in the original recommendation:
The rewritten HEARTBEAT GUIDANCE now recommends: maintain a clock-skew-free lead estimate from the authoritative returned Same unreleased 0.1.25.16 change — no additional version bump; description text only, no schema change. Changelog entry updated, merged artifacts regenerated, all validators + spectral clean. |
…beat margin, stop conditions Changes-requested review on c52b71b, three findings: P1 — the lead formula seeded from the requested ttl_ms, but tenant policy (max_reservation_ttl_ms, default 1h) silently caps the granted TTL and the create response exposes neither the effective TTL nor a server-time anchor: a 24h request capped to 1h would schedule the first beat ~12h late. Guidance now says servers MAY cap ttl_ms by policy; clients SHOULD derive effective_ttl_ms ~= expires_at_ms minus the HTTP Date response header (both server-frame, clock-skew-free; +/-1s resolution negligible), clamped to [1000, requested ttl_ms], and use it for the beat interval, lead seed, extension amount, and skip threshold. A first-class effective_ttl_ms (or server-time) response field is noted as a candidate additive improvement. P2 — the ">= ttl of retry margin" claim was false for the first beat (only ~ttl/2 remains) and degrades under scheduling delay; weakened to apply after the first successful extension. P2 — heartbeat stop conditions now include TENANT_CLOSED (closure is irreversible) and NOT_FOUND alongside RESERVATION_EXPIRED, RESERVATION_FINALIZED, and MAX_EXTENSIONS_EXCEEDED. Changelog entry updated, merged artifacts regenerated. Same unreleased 0.1.25.16 change; description text only, no schema change.
|
All three findings addressed in 02d1270: 1. [P1] Lead formula seeded from requested 2. [P2] "Retries occur with ≥ ttl of margin" overstated. Weakened as suggested: the margin guarantee now explicitly applies after the first successful extension; the first extend attempt inherently has only ~ttl/2 of margin. 3. [P2] Missing stop conditions. Also: the PR description was stale (still described the alternate-beat pattern and the old [ttl/2, 1.5×ttl] band) — rewritten to describe the lead-estimate guidance accurately. Changelog entry updated, merged artifacts regenerated; |
…und; declare Date header Third changes-requested review round (on 02d1270), two findings, both confirmed: P1 — HTTP Date is NOT a safe same-clock anchor for the effective TTL. Per RFC 9110 (sec. 6.6.1 / 5.6.7) Date is a whole-second, best-effort origination timestamp that may be generated at any point during message origination or be replaced by intermediaries; and in the reference server the two values genuinely come from different clocks — expires_at_ms from Redis TIME inside the Lua scripts, Date from the servlet container. Calling the error "±1s negligible" was wrong, and clamping the estimate up to 1000ms fabricated lease. The header was also referenced by the guidance while undeclared in the OpenAPI responses. P2 — the ">= ttl of margin after the first successful extension" claim is false under post-extension clamping (a clamp to 1.2x ttl leaves the next half-ttl beat only 0.7x ttl of lead) and under request latency. The HEARTBEAT GUIDANCE is rewritten around a conservative lead lower bound that uses no cross-clock arithmetic anywhere: - lead_min = sum(measured grants) - client-monotonic elapsed since the reserve response, starting at 0; each grant is the difference of successive returned expires_at_ms values (same server frame, so the differencing is legitimate). lead_min never overstates the true lead and is latency-conservative. - Because lead_min starts at 0, the first extension fires early after a small bounded delay (min(requested ttl/2, 30s, half the optional Date-derived estimate when available)), establishing measured margin and revealing the actual per-extend grant. - Subsequent cadence derives from the measured grant (~last_grant/2, bounded), so server clamps automatically tighten the beat; skip when lead_min >= 1.5x last_grant. - Date is reframed as an OPTIONAL cadence hint for the first-beat delay only — never load-bearing, never clamped upward — with an explicit RFC 9110 caveat and the different-clocks caveat. Since the guidance still references it, the Date response header is now DECLARED as a documented optional header on the createReservation and extendReservation 200 responses (documentation of standard HTTP behavior; no wire change). - Fixed-margin promises replaced by invariants: attempts occur while lead_min >= the beat interval whenever grants keep pace with elapsed time; total protected runtime equals the initial effective TTL plus the sum of grants regardless of schedule; failed attempts consume lead_min and retry at the current cadence with the SAME idempotency_key. - The additive-field proposal is elevated: a future revision SHOULD add an explicit effective-TTL (or server-time) field to ReservationCreateResponse — the only complete fix for the first-beat window. Stop-condition list and idempotency-reuse text retained. Changelog entry updated, merged artifacts regenerated. Same unreleased 0.1.25.16 change; description text plus response-header documentation only, no schema or wire change.
|
Both round-3 findings confirmed and addressed in 00c7a7d. The guidance is rewritten around a conservative lead lower bound with no cross-clock arithmetic anywhere. 1. [P1]
2. [P2] "≥ ttl of margin after the first successful extension" is false under post-extension clamping and latency. Correct — if the server clamps an extension so the lead lands at 1.2×ttl, the next beat at ttl/2 arrives with only 0.7×ttl of lead, and request latency erodes whatever remains. All fixed-margin promises are removed. Replacements:
Also per the round-3 direction: the additive-field proposal is elevated into the guidance itself — a future revision SHOULD add an explicit effective-TTL (or server-time) field to Changelog entry rewritten, merged artifact regenerated; |
…mp regime Fourth changes-requested review round (on 00c7a7d), two findings, both confirmed: P1(a) — any bounded first-beat delay can outlive a small capped initial lease. A 24h request silently capped to a 1s effective TTL is long expired when a min(ttl/2, 30s) first beat arrives, and no field of the create response bounds the effective initial lease. Since the Date hint is non-load-bearing by construction, the only assumption-free default is an IMMEDIATE first extension. P1(b) — under MAXIMUM-LEAD clamping (server holds expiry ~= now + L), successive expires_at_ms differences measure elapsed time between calls, not lease size. Grant-derived cadence (~grant/2) is then a feedback loop that collapses toward any cadence floor and burns the extension budget (default 10) in ~10s; an immediate first extension can even measure a zero grant. "Clamps self-correct" holds only for per-extend GRANT clamping — the previous guidance conflated the two regimes. The HEARTBEAT GUIDANCE is reworked accordingly (same unreleased 0.1.25.16, docs-only): - FIRST extension SHOULD be IMMEDIATE. Costs one extension from the budget; total protected runtime (initial effective TTL + sum of grants) is unchanged by schedule. The bounded-first-delay recommendation and the Date hint's scheduling role are removed entirely; the Date header declaration remains purely as response documentation and the guidance no longer uses it for anything. - Cadence split BY REGIME, distinguished by comparing each measured grant to elapsed time since the previous successful extension: (a) grant >> elapsed (normal, or per-extend grant clamp): beat at ~grant/2 — tightening is correct here; (b) grant ~= elapsed or grant <= 0 (maximum-lead clamp): NO cadence signal exists in the current wire contract — hold a fixed bounded cadence (e.g. min(requested ttl/2, 30s)), MUST NOT tighten toward a floor, SHOULD log the coming budget depletion, and rely on MAX_EXTENSIONS_EXCEEDED as the terminal signal. - lead_min lower bound and the skip rule kept (skip rule noted as meaningful only in regime (a)). - LIMITATION stated plainly: under maximum-lead clamping correct cadence is UNDECIDABLE from current wire data. The additive proposal is elevated on that basis: a future revision SHOULD add an effective-lease field (effective TTL, or expires-relative lease info plus a server-time anchor) to BOTH ReservationCreateResponse AND ReservationExtendResponse — REQUIRED for well-defined client keep-alive behavior under lead clamping, not an optimization. - Stop conditions and idempotency-key reuse text retained. Changelog entry updated; merged artifact regenerated; validators and spectral clean. No schema or wire change; semantic_base remains 0.1.25.
|
Both round-4 findings confirmed and addressed in b166911. I concede the central error explicitly: the previous revision conflated per-extend GRANT clamping with MAXIMUM-LEAD clamping and asserted "a server clamp automatically tightens the beat" as if it covered both. It covers only the former. (a) [P1] A bounded first-beat delay can outlive a small capped initial lease. Correct. The round-3 scheme scheduled the first beat at (b) [P1] Grant-derived cadence collapses under maximum-lead clamping. Correct, and this is the conflation conceded above. When the server holds expiry ≈ now + L, each successive The guidance now splits cadence by regime, distinguished by comparing each measured grant to the elapsed time since the previous successful extension:
The Consequence, stated plainly in the spec: under maximum-lead clamping, correct heartbeat cadence is UNDECIDABLE from data currently on the wire — no combination of returned Validation: |
… alone Cross-SDK implementation review (defect found by the Rust implementation, fix now shipped in all four SDKs) caught a misclassification in the round-4 regime-detection rule as written. Defect: an upper-bound-only test (grant <= ~1.25 x elapsed since the previous successful extension) misclassifies a genuine per-extend-GRANT-clamped server after a lead_min skip. The next measured grant arrives across a DOUBLED gap, so at that instant grant ~= elapsed exactly for a server whose cadence is grant/2; the client flips to the held lead-clamp cadence, and at the held cadence the ratio stays <= 1.25 forever — the misclassification self-sustains while the lease decays to a lapse, contradicting the guidance's own rule that grant-clamped leases keep tightening. Amendment (same unreleased 0.1.25.16, docs-only): regime detection SHOULD test that the grant sits INSIDE a band around elapsed, e.g. 0.75 x elapsed <= grant <= 1.25 x elapsed, in addition to grant <= 0 always indicating clamping and grant >= ~0.9 x requested ttl_ms always indicating the normal regime. Rationale stated in the guidance: a genuine maximum-lead clamp tracks ANY inter-success gap (grant/elapsed ~= 1 at every cadence), while a real small grant seen across a skip-doubled gap matches the band at most once — at the held cadence its ratio falls below the lower edge and cadence re-tightens — so with the band test misclassification is transient, never sticky. Changelog entry wording updated in place; merged artifact regenerated; validators and spectral clean. No schema or wire change; semantic_base remains 0.1.25.
|
Follow-up amendment in 692e06f (same 0.1.25.16, docs-only): cross-SDK implementation review — the defect was found by the Rust implementation, and the fix is now shipped in all four SDKs — caught a hole in the regime-detection rule as written in b166911. Defect: detecting the lead-clamp regime by an upper bound alone (grant ≤ ~1.25×elapsed-since-last-success) misclassifies a genuine per-extend-GRANT-clamped server after a Fix: regime detection SHOULD test that the grant sits inside a band around elapsed — e.g. 0.75×elapsed ≤ grant ≤ 1.25×elapsed — with grant ≤ 0 always indicating clamping and grant ≥ ~0.9×requested ttl_ms always indicating the normal regime. The band discriminates because a genuine maximum-lead clamp tracks ANY inter-success gap (grant/elapsed ≈ 1 at every cadence), while a real small grant seen across a skip-doubled gap matches the band at most once: at the held cadence its ratio falls below the lower edge and cadence re-tightens. Misclassification becomes transient, never sticky. Guidance text and changelog wording updated in place; |
…heartbeat algorithm Round-5 review verdict accepted; the PR is no longer docs-only. Two concessions drove the rework: 1. The band heuristic for regime detection is formally undecidable from the observables (grant, elapsed). Counterexample: requested ttl 24s, per-extend grant cap 10s -> post-skip held cadence min(ttl/2, 30s) = 12s -> ratio 10/12 ~= 0.833 sits inside [0.75, 1.25] FOREVER while the lease erodes 2s per cycle to a lapse. Any true grant in [0.75 x min(ttl/2, 30s), 0.9 x ttl) stays misclassified permanently, and grant >= 0.9 x ttl does not prove the normal regime either. 2. "Nothing is lost but the count" was false under maximum-lead clamping: extension value is schedule-dependent (immediate extend ~= zero grant vs the same extend 30s later ~= 30s grant with a 60s max lead), so total protected runtime and the sum of grants are NOT schedule-invariant. Changes (same unreleased 0.1.25.16; additive optional field, backward compatible, semantic_base stays 0.1.25): - Add optional remaining_ttl_ms (int64, >= 0) to BOTH ReservationCreateResponse and ReservationExtendResponse: remaining lifetime at response evaluation, same authoritative clock snapshot as expires_at_ms; present on every successful live-reservation response (absent on dry-run and DENY). Servers SHOULD emit; clients MUST treat as optional. - HEARTBEAT GUIDANCE: NORMATIVE scheduling algorithm when remaining_ttl_ms is present — recompute from every successful create/extend response, never accumulate expiry differences: rtt (unknown -> 0); lead_floor = max(0, remaining_ttl_ms - rtt); retry_reserve = min(lead_floor/2, max(1000 ms, 2 x max observed rtt)); next_delay = max(0, lead_floor - retry_reserve). First beat derives from the CREATE response the same way — no immediate priming, no wasted extension. Transient failures retry with the SAME idempotency_key after clamp(current_lead_estimate/4, 1s, 30s); any 2xx counts as applied. - Measured-grant/band scheme demoted to an explicitly NON-NORMATIVE fallback with limits stated plainly: only sound against per-extend DELTA clamps; regime detection undecidable (sticky-band counterexample included); no portable, safe, extension-efficient heartbeat exists without remaining-lifetime data — that is why remaining_ttl_ms exists. Fallback clients prefer over-beating over lease lapse. - Immediate priming reframed as a tradeoff (fallback only): minimizes unknown-initial-lease lapse risk at the cost of one potentially zero-value extension; the earlier free-action claim retracted in the text. - Changelog entry rewritten (now a wire-contract addition + guidance); spec-index comments updated (no longer "documentation-only"); merged artifact regenerated; validators and spectral clean.
|
Round-5 verdict accepted in full; both P1s conceded, and the reviewer's clean solution is adopted in a681e8c. The PR is no longer docs-only — title and body updated. 1. [P1] The band heuristic is formally undecidable — conceded, with your counterexample now in the spec text. Requested ttl 24s, per-extend grant cap 10s: after a lead_min skip the grant is measured across a doubled gap (ratio ≈ 1, inside the band), the client adopts the held cadence min(24s/2, 30s) = 12s, and at that cadence the ratio is 10/12 ≈ 0.833 — inside [0.75, 1.25] forever, while the lease erodes 2s per cycle to a lapse. My "matches the band at most once" claim was simply wrong: the entire window grant ∈ [0.75×min(ttl/2, 30s), 0.9×ttl) stays misclassified permanently once the held cadence is adopted, and grant ≥ 0.9×ttl does not prove the normal regime either (a maximum-lead clamp echoes any call spacing, including one approaching the requested ttl). No refinement of thresholds fixes this — regime detection from the observables (grant, elapsed) is undecidable in general, and the spec now says so plainly. 2. [P1] "Nothing is lost but the count" is false under maximum-lead clamping — conceded. Extension value is schedule-dependent: with a 60s maximum lead, an immediate extend measures a grant ≈ 0 where the same extend fired 30s later would have measured ≈ 30s. Total protected runtime and Σ grants are therefore NOT schedule-invariant, and immediate priming can spend one extension for zero added lifetime. The guidance now presents immediate priming (fallback path only) as exactly that tradeoff — minimized unknown-initial-lease lapse risk at the cost of one potentially zero-value extension — and explicitly retracts the earlier free-action claim in the text. Adopted solution, per your recommendation: optional Correction to my earlier wording: the band fix is not "shipped in all four SDKs" — it is implemented on the four open SDK PR branches (py#90, ts#173, rust#75, java#110; none merged). Those branches will move to the Versioning: the addition rides the same unreleased 0.1.25.16 (optional-field additive; |
… heartbeat timing
Resolves the conflict between the idempotent-replay normative ("replay
MUST return the ORIGINAL successful response payload") and the intended
remaining_ttl_ms semantics, matching the server implementation
(cycles-server#260):
- EXTEND replays: remaining_ttl_ms is a volatile response observation.
When emitted on a same-key replay of a successful extend, the server
MUST recompute it as max(0, original expires_at_ms - current
authoritative server time) while constructing the replay response,
never copy the stored value; all other fields replay verbatim; it
MUST be 0 if the reservation is no longer ACTIVE. Rationale in the
text: a heartbeat retrying a lost extend with the same idempotency
key schedules its next beat from the replayed body, and a cached
value is stale by the retry delay — scheduling from it would
overshoot the real lease.
- CREATE replays: NO carve-out. The original body — including the
original remaining_ttl_ms — is returned verbatim, because the emitted
CyclesEvidence envelope references the original response body.
Clients SHOULD NOT treat a replayed create's value as current.
- Carve-out stated at every replay-verbatim MUST: top-level IDEMPOTENCY
in info.description, the IdempotencyKeyHeader parameter, the
createReservation IDEMPOTENCY block (explicit verbatim + evidence
rationale), and the extendReservation IDEMPOTENCY block.
- HEARTBEAT GUIDANCE gains replay awareness: same-key extend retries
are safe to schedule from (server recomputes); a create response
arriving after same-key retries may be a verbatim replay, so its rtt
MUST be measured from the FIRST attempt's monotonic send time,
bounding the staleness fed into lead_floor.
Also includes the working-tree hardening of the primary scheduling
algorithm (per-attempt monotonic RTT with conservative unknown-timing
handling, attempt_budget/safety_margin-based retry_reserve, schema-
valid-200-only success rule, bounded 429/Retry-After handling, and the
zero-delay loop guard) and the matching changelog wording.
Changelog entry updated in place; merged artifact regenerated;
merge_specs / validate_changelogs / validate_spec_index_versions /
spectral all clean. Same unreleased 0.1.25.16; additive semantics
unchanged; semantic_base remains 0.1.25.
|
785c2f6 adds an idempotent-replay carve-out for |
Recompute volatile remaining TTL on create and extend replays, exclude it from reserve evidence, and make retry budgets conservative for unknown timing, ambiguous responses, rate limits, and exhausted recovery windows.
Convert Retry-After seconds before comparing with millisecond retry windows, make duration math overflow-safe, and cover lost CREATE responses with bounded same-key recovery.
Require conservative duration rounding so SDK timer granularity cannot consume the heartbeat safety margin.
Allow additional same-key retries while the recomputed deadline remains safe, document timeout-to-lease constraints and no-clock behavior, explain fallback asymmetry, and narrow Retry-After to delta-seconds.
Summary
Add an optional
remaining_ttl_msfield to reservation create/extend responses and define a deadline-safe heartbeat algorithm forextendReservation/extend_by_ms. The measured-grant scheme remains only as an explicitly non-normative fallback for older servers.This is an additive wire-contract change in the still-unreleased runtime revision 0.1.25.16.
semantic_baseremains 0.1.25.Why
extend_by_msis relative to the reservation's currentexpires_at_ms, not request time. A client that extends by the full TTL everyttl/2drifts expiry outward and burns the extension budget. Servers may also clamp the per-extension grant or clamp expiry to a maximum lead; those regimes cannot be identified safely from expiry deltas alone. A server-authoritative remaining-lifetime observation is therefore required for portable heartbeat scheduling.Contract changes
remaining_ttl_ms(int64, minimum 0) toReservationCreateResponseandReservationExtendResponse, computed from the same authoritative clock used for expiry decisions. It is absent on dry-run and DENY create responses.remaining_ttl_msas volatile transport metadata. On same-key CREATE or EXTEND replay, servers that emit it MUST recomputemax(0, original expires_at_ms - current authoritative server time), never replay the stored value, and return 0 if the reservation is no longer ACTIVE. All other outcome fields replay verbatim.remaining_ttl_msfrom the signed reserve evidence payload, alongsidecycles_evidence, so replay-time recomputation does not change the original evidence identity. The companion evidence spec is revised to 0.2.2 with no envelope wire-shape orschema_versionchange.extendReservation, including non-negative delta-secondsRetry-AfterandX-RateLimit-Reset. HTTP-dateRetry-Afteris intentionally invalid because safe conversion would require wall-clock assumptions.Dateout of lease arithmetic; it is documented only as standard HTTP metadata.Normative heartbeat algorithm when
remaining_ttl_msis presentFor every schema-valid HTTP 200 create or extend response:
lead_floor = max(0, remaining_ttl_ms - rtt).attempt_budget = max(enforced request_timeout_budget, 1000 ms, 2 * maximum observed rtt).safety_margin = max(1000 ms, 2 * maximum observed rtt).retry_reserve = 2 * attempt_budget + safety_margin.next_delay = max(0, lead_floor - retry_reserve); schedule from response receipt.A positive delay requires
lead_floor > retry_reserve. Clients should configure timeout and expected-latency bounds soattempt_budget < (minimum expected lead_floor - safety_margin) / 2. With a 60000 ms returned lead, a 10000 ms timeout and 1500 ms maximum observed RTT produce a 23000 ms reserve and approximately 37000 ms cadence. A 30000 ms timeout produces at least a 61000 ms reserve and zero delay. An additive-delta server may establish more lead on the immediate extension; a 60000 ms maximum-lead clamp cannot.All duration arithmetic uses milliseconds and is overflow-safe; positive overflow is treated as infinity, never wraparound. Where timer resolution requires rounding, clients round budgets/margins up and lead lower bounds/delays down so rounding cannot consume the reserved margin.
Unknown or unreliable attempt timing forces
lead_floor = 0andnext_delay = 0. An unknown or unbounded request timeout makesattempt_budgetinfinite. A client without reliable monotonic attempt timing therefore cannot establish a safe primary-path cadence: after one immediate fresh extension, the two-consecutive-zero guard requires it to stop. It cannot switch to the fieldless fallback merely because local timing is unavailable.Only schema-valid HTTP 200 responses count as observed success. Other or malformed 2xx responses are ambiguous and cannot drive scheduling. After a CREATE timeout, connection error, 5xx, or ambiguous 2xx, the client gets at most one immediate same-key recovery attempt.
For an EXTEND timeout, connection error, 5xx, 429, or ambiguous 2xx:
current_lead_estimatefrom the last valid response and monotonic elapsed time.retry_window = current_lead_estimate - attempt_budget - safety_margin, without clamping.min(30000 ms, current_lead_estimate / 4, retry_window).Retry-Afterto milliseconds with overflow checking and retry only when the converted delay fits withinretry_window; clients do not invent an earlier retry that violates throttling.retry_windowis positive; elapsed time from prior attempts is already deducted and every decision reserves the next complete attempt plus margin.retry_windowdecreases between failures, preventing a zero-time loop.A zero-delay success permits at most one immediate fresh-key extension attempt. If that success also produces zero delay, the client stops instead of burning a maximum-lead server's extension budget in a tight loop.
Fallback
When a server does not emit
remaining_ttl_ms, the measured-grant/band guidance is NON-NORMATIVE and explicitly documents its limits. Clamp-regime detection is undecidable in general; fallback clients should prefer bounded over-beating and visible extension-budget depletion over risking a lease lapse.The asymmetry is intentional: a field-bearing client with reliable timing can prove when no complete attempt plus margin fits and stops. A fieldless client cannot prove safety or lapse, so bounded over-beating is an explicitly best-effort choice that favors avoiding an unobservable lapse at the cost of extension-budget burn.
Files and versions
Validation
Retry-Afterseconds-to-milliseconds boundary and overflow scenarios: passedDownstream implementation impact
cycles-server: emit/recompute the volatile field, exclude it from reserve evidence, and expose the documented 429 response.ReservationCreateResponseMirrorpayloads.Future option
An absolute-target extension such as
set_expiry_tocould make keep-alive idempotent with respect to drift, but it is outside this change.