Repository navigation
Conversation
Bring the idempotency plugin up to the-middleman's standard. Code: - Migrate Redis config to the shared kong.tools.redis config.redis.* record (Kong 3.6+), keeping legacy flat redis_* keys via shorthand_fields. - Proper connection lifecycle (get_reused_times-gated AUTH/SELECT, per-DB pool, set_keepalive release); fail open on Redis errors instead of crashing. - Only the lock-winning request persists the response (kong.ctx.plugin.store), so duplicates/passthrough skip Redis and the TTL window stays fixed. - Extract pure keys.lua (single source of truth for key format). - Strip volatile headers (content-length/transfer-encoding/connection) on cache. - Fix bogus rockspec dependency (redis-lua -> none; resty.redis ships with Kong). Tests & tooling: - Unit (mocked) + integration (real Kong + Redis via Pongo): 48 tests. - Add .busted, Makefile, .pongo/pongorc, CI matrix (Kong 3.6.1/3.8.0/3.9.2). Docs: rewrite README; pin playground to kong:3.9.2. Bump to 1.2.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adversarial testing surfaced correctness/security issues in the cache key: - Cross-consumer leak: the key was not scoped by the authenticated consumer, so consumer B reusing consumer A's X-Idempotency-Key received A's cached response. The key now includes the consumer id (or "anonymous"). - Cross-host leak: the key did not include the request host, so the same key on different hosts collided. The key now includes the host. - Empty key: an empty X-Idempotency-Key header (truthy "") was treated as a valid shared key, leaking responses across unrelated requests. It is now treated as no key (passthrough, or 400 when required). - Fractional TTL: redis_cache_time was a "number"; a fractional value made `SET ... EX` fail at runtime, silently disabling idempotency. It is now an integer, rejected at config time. New key format: [user::]prefix:<consumer>:<host>:<path>:<method>:<key>. Specs updated; suite now 55 tests (lint clean) on Kong 3.6.1/3.9.2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- DB-less Kong 3.9.2: the whole setup lives in kong.yml; drop Postgres, migrations, the Node loader and the custom image (plugin is volume-mounted). - service.js: JSON echo with a per-request X-Upstream-Id plus control headers (X-Echo-Status/Delay/Header/Binary/Gzip) used by the scripts. - show-config.sh: prints kong.yml + the loaded config via the Admin API. - demo.sh: step-by-step walkthrough that also asserts (smoke test). - edge.sh: adversarial probes (concurrency, sticky 5xx, binary/gzip bodies, empty key, host/path/method scoping, TTL expiry) labelled PASS/NOTE/BUG. - explicit compose project name to avoid collisions with other playgrounds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two more issues found via chaos testing: - 500 on cache-key collision: an idempotency key ending in "-response" produced a lock key equal to another key's response key, and the unguarded cjson.decode of that non-object value crashed the request. Lock and response keys now use a fixed 🔒 / :resp: discriminator before the key (collision impossible), and the decode is wrapped in pcall + a type check (a corrupt or foreign value yields 409, never a 500). - Orphaned lock on upstream failure: when the original request acquired the lock but never cached a response (e.g. the upstream reset the connection), the lock blocked every retry with 409 for the whole TTL. The lock is now released in the log phase via ngx.timer.at (Redis cosockets are disabled in log_by_lua, so the delete is deferred to a timer). Suite now 60 tests (lint clean) on Kong 3.6.1/3.9.2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- service.js: X-Echo-Reset (drop the connection mid-flight) and X-Echo-Gzip. - edge.sh: probes 11-13 — a "-response" key does not crash the plugin, an upstream failure frees the lock so retries succeed, and a client disconnect still serves the cached result on retry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The kong-pongo executable is `pongo.sh`; adding the clone dir to PATH did not provide a `pongo` command (exit 127). Symlink pongo.sh -> ~/.local/bin/pongo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Silences the Node.js 20 deprecation warning; checkout@v5 runs on Node.js 24. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (v1.3.0) Four configurable behaviours: - verify_fingerprint (default true): store an md5 of the request body + query as the lock value; reusing a key with a different request returns 422 (conflict) instead of replaying the original response. - methods (default ["POST"]): apply idempotency to POST/PUT/PATCH/DELETE. - cache_5xx (default false): do not cache 5xx responses; the lock is released so the client can retry after a transient server error. - fail_open (default true): when false, a Redis outage is rejected with 503 instead of passing the request through. Bump to 1.3.0. Suite now 72 tests (lint clean) on Kong 3.6.1/3.9.2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mode - kong.yml: /multi (POST/PUT/PATCH/DELETE) and /strict (fail_open=false) routes. - edge.sh: probes 14-17 — 422 on key reuse with a different body, 5xx not cached (retry reprocesses), PUT idempotent on /multi, and 503 vs passthrough with Redis stopped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add tests for behaviours that were untested (or only exercised in the playground's edge.sh): - unit: cache.del(); fingerprint includes the query string; fingerprint fallback when the body is unavailable (large/spooled); 503 on a GET error in strict mode. - integration: PUT idempotency (methods); strict mode 503 vs fail-open passthrough against an unreachable Redis; per-consumer scoping (key-auth, two consumers, same key -> no cross-consumer leak). Also reject an empty `methods` list (len_min=1) so the plugin cannot be silently disabled. Suite now 82 tests (lint clean) on Kong 3.6.1/3.9.2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- fix the POST-only wording: idempotency now covers POST/PUT/PATCH/DELETE via config.methods. - state that the client owns idempotency-key uniqueness. - add Requirements (Kong >= 3.6, Redis) and a worked curl Example showing the completed / conflict (422) / waiting_response (409) flows. - note ordering (runs after auth, enabling per-consumer scope) and response buffering; point fail_open=false at the 503 behaviour. - refresh the integration-tests description and add a License section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Major bump: breaking changes vs the 1.x line — the Redis key format now scopes by consumer/host (and uses 🔒/:resp: discriminators), the config moved to Kong's shared config.redis.* record, and verify_fingerprint defaults to on (reusing a key with a different request now returns 422). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GitHub renders Mermaid natively, so the request-flow decision tree (passthrough, 400, completed, replay, 422 conflict, 409 in-flight) is visible at a glance, with colours mapping to the X-Idempotency-Status / HTTP outcome. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… each
Three deepenings from an architecture review. Each takes a concept that was
smeared across phases and gives it a single module that owns it.
- lifecycle: the {store, lock_key, cached} protocol the access/response/log
phases coordinated through on kong.ctx.plugin. The "free an orphaned lock?"
rule now lives in lifecycle.orphaned_lock() instead of inline in the handler;
only the timer + Redis del IO stays at the edge.
- scope: the {host, path, method, consumer} descriptor was rebuilt inline in
both the access and response phases. scope.from_request() builds it once and
feeds the still-pure keys module.
- payload: encode + the volatile-header strip lived in response, decode +
validation in access. payload now owns the stored-response contract end to
end; access and response no longer touch cjson directly.
New pure unit specs cover each module's interface. The existing
access/response/handler specs are unchanged: field names stayed stable, so they
still verify the resulting state. Adds CONTEXT.md recording the domain
vocabulary the modules are named after.
Full suite (unit + integration, real Kong + Redis): 99/0/0/0; luacheck clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…is variant Expand the playground from happy-path + resilience coverage to exhaustive behavioural coverage. New routes/consumers in kong.yml and a password-protected Redis in docker-compose; edge.sh grows from 17 to 26 probes. Config flags, both states: - cache_5xx=true (/cache5xx) and verify_fingerprint=false (/nofp); the redundant edge probe #6 is repurposed to test cache_5xx=true (it was duplicating #15). Scoping (all three isolation dimensions): - per-consumer (/auth, key-auth + alice/bob consumers), host, and the existing path. Redis connection variants (previously only mock-tested): - non-default logical database (/db, redis.database=1; keys verified in db 1); - AUTH against a password-protected Redis (/redis-auth). Methods & fidelity: - PATCH and DELETE on /multi; gzip replay via the advertised X-Echo-Gzip hook (Content-Encoding preserved byte-for-byte). Defensive paths: - corrupt cached value -> 409 (payload.decode), empty key on /required -> 400. show-config.sh now lists consumers; README routes/probe list updated. Verified live: demo.sh 11/0, edge.sh 26 probes, 0 BUG / 2 NOTE (by-design method/path). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Summary
Brings the
idempotencyplugin up to a production-ready standard for Kong 3.9.2, mirroring the conventions ofkong-plugin-the-middleman: shared Redis schema, a full test suite, and a DB-less playground. Adversarial / chaos testing along the way surfaced several real bugs, all fixed and covered by tests.Kong 3.9.2 readiness
config.redis.*record (kong.tools.redis.schema, Kong 3.6+) — vault-referenceable credentials, TLS/SNI, per-DB pooling. Legacy flatredis_*keys still accepted viashorthand_fields.get_reused_times-gated AUTH/SELECT, per-DB pool name,set_keepaliverelease. Fail open (log + passthrough) on Redis errors instead of crashing.redis-lua→ none;lua-resty-redisships with Kong). Bumped to1.2.0.Bugs found & fixed (via unit + chaos testing)
X-Idempotency-Keyreceived A's cached response. Key now includes the consumer id.X-Idempotency-Key("", truthy in Lua) was a shared key. Now treated as no key.redis_cache_time: schema acceptednumber, so a value like1.5madeSET ... EXfail at runtime and silently disabled idempotency. Nowinteger, rejected at config time.-responsecollided lock/response slots, and the unguardedcjson.decodeof the resulting non-object value crashed the request. Lock/response keys now use a fixed:lock:/:resp:discriminator (collision impossible) and the decode ispcall-guarded + type-checked (corrupt value → 409, never 500).409for the whole TTL. The lock is now released in thelogphase viangx.timer.at(Redis cosockets are disabled inlog_by_lua).New key format:
[user::]prefix:<consumer>:<host>:<path>:<method>:{lock|resp}:<key>.Tests & tooling
.github/workflows/test.yml);Makefile+.busted+.pongo/pongorc.DB-less playground
kong.yml; no Postgres/migrations/custom image).show-config.sh,demo.sh(walkthrough + assertions), andedge.sh(13 adversarial probes incl. concurrency, sticky 5xx, binary/gzip bodies, key collision, upstream failure, client disconnect) — all green (0 bugs).Known limitations (documented in README)
POSTis handled.5xxis cached for the window (sticky); could be made configurable.🤖 Generated with Claude Code