Skip to content

refactor(idempotency): cache only successes; drop the vestigial status column (#96)#119

Merged
JumpTechCode merged 1 commit into
mainfrom
fix/idempotency-cache-only-successes
Jul 20, 2026
Merged

refactor(idempotency): cache only successes; drop the vestigial status column (#96)#119
JumpTechCode merged 1 commit into
mainfrom
fix/idempotency-cache-only-successes

Conversation

@JumpTechCode

Copy link
Copy Markdown
Contributor

ADR 0007 (Accepted) said to cache successes and hard validation rejections, and reserved a status column to tell them apart. Only the success half was ever built: every denial — insufficient budget, unknown model, unauthorized/unknown scope, key reuse — raises and rolls its transaction back, so claim decides FRESH/REPLAY/MISMATCH from command_fingerprint and response alone and never reads status. The column has one value forever ('succeeded'), so the ADR, the schema, and the code disagree.

The as-built behaviour is also the safer policy (as the issue notes): caching a hard rejection would pin a stale answer past truth — an unknown-model rejection would keep replaying even after a later price-book version adds that model, the same stale-cache hazard ADR 0007 forbids for budget denials.

Changes

No behavior change

The column was never read; nothing that decides an outcome depended on it. Integration checks that asserted "a success persisted its key" now key on response IS NOT NULL — equivalent, since a success is the only outcome that caches a response.

Local gate: ruff, mypy --strict, import-linter, full unit (497), full integration (182, incl. the migration-drift check) — all green.

Closes #96.

…s column (#96)

ADR 0007 said to cache successes AND hard validation rejections and reserved a
`status` column to tell them apart. Only the success half was ever built: every
denial (insufficient budget, unknown model, unauthorized scope, key reuse)
raises and rolls its transaction back, so `claim` decides FRESH/REPLAY/MISMATCH
from `command_fingerprint` and `response` alone and never reads `status`. The
column had one value forever ('succeeded') and the ADR, schema, and code
disagreed.

Caching only successes is also the safer policy: a cached hard rejection (e.g.
unknown-model) would pin a stale answer even after a later price-book version
adds the model — the same stale-cache hazard ADR 0007 forbids for budget
denials.

- ADR 0038 supersedes ADR 0007, recording the as-built "cache only successes"
  policy; ADR 0007 marked Superseded.
- Drop the write-only `status` column from both idempotency tables
  (idempotency_key and the durable metered_receipt) via migration 0007;
  store_response no longer takes a status argument, and the RESPONSE_SUCCEEDED
  constant is removed.

No behavior change: the column was never read. Integration checks that asserted
a persisted success now key on `response IS NOT NULL` (equivalent, since only a
success caches a response).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjJ4QScUKbrMzF1jujL5Bg
@JumpTechCode
JumpTechCode merged commit 31765e5 into main Jul 20, 2026
8 checks passed
@JumpTechCode
JumpTechCode deleted the fix/idempotency-cache-only-successes branch July 20, 2026 21:45
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.

Idempotency caching policy: ADR 0007 says cache hard rejections, but the code caches only successes

1 participant