fix(api,domain): idempotency/records/docs cleanup (#100, #105, #106, #107)#118
Merged
Merged
Conversation
…107) - #100: only reserve can raise InsufficientBudget (402); commit records overage, cancel releases, extend touches no balance. Give commit/cancel/extend the no-402 response set (renamed _NO_BUDGET_DENIAL_RESPONSES, shared with meter/grace), so SDKs generated from the OpenAPI schema stop carrying dead 402 branches. - #105: IdempotencyClaim.response is now deep-frozen (nested mappings -> MappingProxyType, sequences -> tuple) instead of a shallow top-level view, so a nested cached response can't be mutated through the proxy. - #106: reserve_fingerprint folds "command": "reserve" like every other command, so two command kinds can never collide under one key (defense-in-depth). - #107 (remaining nits): find_lines returns lines already in the canonical lock order so deadlock-freedom is self-enforcing at the source; the Idempotency-Key header is trimmed of surrounding whitespace (a blank key is rejected) so padded variants dedupe; store_response fails loud on a rowcount != 1 instead of silently dropping the response; and the idempotency_repo docstring is corrected to the composite (principal_id, key) primary key / ON CONFLICT. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BjJ4QScUKbrMzF1jujL5Bg
This was referenced Jul 20, 2026
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.
A cluster of localized correctness and doc fixes.
#100 — commit/cancel/extend advertise a 402 they can never return
Only
reservecan raiseInsufficientBudget(it's the sole admission gate);commitrecords overage rather than denying,cancelonly releases,extendtouches no balances.commit/cancel/extendnow use the no-402 response set (the constant is renamed_NO_BUDGET_DENIAL_RESPONSESand shared with meter/grace), so SDKs generated from the OpenAPI schema stop carrying dead 402-handling branches.#105 — IdempotencyClaim.response was only shallowly immutable
responsewas wrapped in a top-levelMappingProxyType, leaving nested lists/dicts mutably aliased. It's now deep-frozen (nested mappings →MappingProxyType, sequences → tuple), so the immutability guarantee matches theMapping[str, Any]type even for a nested cached response.#106 — reserve_fingerprint omitted the "command" discriminator
commit/cancel/meter/graceall fold a"command"key so two command kinds can't collide under one key;reservenow folds"command": "reserve"too — defense-in-depth even though reserve's field set is already structurally disjoint.#107 — remaining review nits
find_linesreturns lines already sorted in the canonical lock order (lock_order_keythenperiod_start, the same keyordered_linesuses), so deadlock-freedom is self-enforcing at the source rather than resting on every caller re-sorting.Idempotency-Keyis trimmed of surrounding whitespace (a blank key → 422), so"k"and" k "are the same cache entry. Case is intentionally left intact (an idempotency key is an opaque token; lowercasing could collide distinct keys).store_responseraises onrowcount != 1(an invariant breach — it only ever runs afterclaiminserted the row in the same tx) instead of silently dropping the response.idempotency_repodocstring corrected: composite(principal_id, key)primary key andON CONFLICT (principal_id, key)(the old text predated per-principal scoping, Idempotency keys are globally namespaced; scope them per principal #71).The worker-backoff-overflow, SdkConfig-validation, and read-route-bound items of #107 were fixed in earlier PRs (#110, #108, #111); this closes the rest.
Tests
Unit: only-reserve-advertises-402; Idempotency-Key trimmed / blank rejected; deep-immutability of a nested claim response; reserve fingerprint folds the command discriminator. Integration:
find_linesreturns canonical order (lines inserted out of order);store_responseon an unclaimed key fails loud.Local gate: ruff, mypy --strict, import-linter, full unit (497), full integration (182) — all green.
Closes #100, #105, #106, #107.