Claude Skill

br-atomic-idempotency

Configure better-route 1.1 AtomicIdempotencyMiddleware for side-effectful POST, PUT, PATCH, or DELETE routes where concurrent duplicate execution must be prevented. Use for WpdbAtomicIdempotencyStore, lease-aware reservations, reservation_token schema migration, Idempotency-Key v

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download Lonsdale201-wp-agent-skills-better-route_br-atomic-idempotency-8820ff3.zip · 3 KB
Part of lonsdale201/wp-agent-skills — 226 skills

Install

skills CLI npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/better-route/br-atomic-idempotency
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install lonsdale201-wp-agent-skills@llmmart
Git git clone https://github.com/Lonsdale201/wp-agent-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole lonsdale201/wp-agent-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

better-route: atomic idempotency

Use atomic idempotency for writes whose side effect must not execute twice under concurrent retries. It reserves before invoking the handler; classic IdempotencyMiddleware only stores after completion.

Production setup

use BetterRoute\Middleware\Write\AtomicIdempotencyMiddleware;
use BetterRoute\Middleware\Write\WpdbAtomicIdempotencyStore;

register_activation_hook(__FILE__, static function (): void {
    (new WpdbAtomicIdempotencyStore())->installSchema();
});

$atomic = new AtomicIdempotencyMiddleware(
    store: new WpdbAtomicIdempotencyStore(),
    ttlSeconds: 86400,
    requireKey: true,
);

$router->post('/payments', $handler)
    ->protectedByMiddleware('bearerAuth')
    ->middleware([$auth, $atomic]);

Run installSchema() for both new installs and 1.0 upgrades. The 1.1 table has a per-reservation reservation_token; the installer creates or migrates that column.

Use ArrayAtomicIdempotencyStore only in tests or non-WordPress single-process checks.

Behavior

  • First request reserves the canonical route + identity + client key and runs the handler.
  • Same key and fingerprint while reserved returns 409 idempotency_in_progress.
  • Same key with a different fingerprint returns 409 idempotency_conflict.
  • Completed identical retry replays status/body/headers and adds Idempotency-Replayed: true.
  • Missing key returns 400 idempotency_key_required when required.
  • Invalid, non-printable, or overlong keys return 400 idempotency_key_invalid; default maximum length is 200.

The default fingerprint deeply canonicalizes route, method, identity, and request params. Associative key order does not change it; list order remains meaningful. Native logged-in WordPress users, auth middleware identities, and HMAC key identities scope defaults safely.

Route scope and 1.1.1 migration

Scope default keys and fingerprints to the router namespace, registered template, concrete request path and separately captured URL parameters, alongside identity and the existing payload fields. Query/body parameters must not mask URL IDs. routePath remains the template. Test two namespaces and two URL targets with the same client key and shadowing query/body IDs; they must remain isolated.

Existing default records cannot safely translate and are not replayed through a legacy fallback. Before switching, pause writers/retries, drain requests, reconcile uncertain business operations and retire old retries across the full client retry horizon. TTL expiry alone and deleting records are insufficient. Switch all workers together; apply the same coordination on rollback. Follow br-install-and-migrate for the full procedure.

Custom key/fingerprint resolvers own equivalent isolation. A custom key alone still uses the changed default fingerprint. After uncertain failures, reconcile before retrying with any key, even after expiry; keep business-level deduplication for irreversible effects.

Failure semantics

In 1.1 releaseOnThrowable defaults to false. A thrown exception can occur after an irreversible external side effect but before the response is recorded, so the reservation remains in_progress until TTL rather than allowing a dangerous automatic retry.

Set releaseOnThrowable: true only if the complete operation is transactional/rollback-safe or the handler is known to fail before any side effect:

new AtomicIdempotencyMiddleware(
    store: $store,
    releaseOnThrowable: true,
);

Returned WP_Error values are not serialized and leave the reservation uncertain. They are normalized after the middleware pipeline.

Stored response contract

The wpdb store serializes a data-only envelope with allowed_classes => false:

  • arrays, scalars, and null are supported;
  • BetterRoute\Http\Response is decomposed into body/status/string headers;
  • WP_REST_Response is normalized into the same safe Response form;
  • arbitrary objects/resources inside the body are rejected.

Do not return domain objects from idempotent handlers; map them to data first.

Lease-aware completion and release include the unpredictable reservation token. A stale request therefore cannot complete or delete a newer reservation that reused the same key after expiry.

WooCommerce registrar

When Woo idempotency is enabled in WordPress, WooRouteRegistrar uses atomic idempotency across orders, products, customers, and coupons. Without a custom store it installs/migrates the wpdb store once per schema version. Installation failure is surfaced; it does not silently degrade to an array store.

'idempotency' => [
    'enabled' => true,
    'requireKey' => true,
    'ttlSeconds' => 86400,
    // 'store' => $customAtomicStore,
],

Any custom Woo store must implement AtomicIdempotencyStoreInterface.

Review checklist

  • Put authentication before atomic idempotency.
  • Install/migrate the wpdb schema before serving traffic.
  • Choose TTL for the longest realistic uncertain/retry window.
  • Keep the default fail-closed releaseOnThrowable: false unless retry safety is proven.
  • Return data-only responses.
  • Load-test two simultaneous identical requests; exactly one handler may run.
  • Test same key with a changed body and verify idempotency_conflict.
  • Use a custom key/fingerprint resolver only when tenant/domain scope cannot be expressed by the default identity-aware canonical form.

Related skills

  • Use br-idempotency for lower-risk replay-cache semantics.
  • Use br-woo-routes for registrar integration.
  • Use br-error-contract for 400/409 response shapes.

References

  • Verified source paths:
    • src/Middleware/Write/AtomicIdempotencyMiddleware.php
    • src/Middleware/Write/WpdbAtomicIdempotencyStore.php
    • src/Middleware/Write/LeaseAwareAtomicIdempotencyStoreInterface.php
    • src/Middleware/Write/StoredResponseCodec.php
Files (wp-agent-skills)
  • agents
    • openai.yaml 261 B
      interface:
        display_name: "Better Route Atomic Idempotency"
        short_description: "Prevent concurrent duplicate write execution."
        default_prompt: "Use $br-atomic-idempotency for concurrent write safety and review namespace/URL replay scope before migration."
      
  • SKILL.md 6.5 KB
    ---
    name: br-atomic-idempotency
    description: Configure better-route 1.1 AtomicIdempotencyMiddleware for side-effectful POST, PUT, PATCH, or DELETE routes where concurrent duplicate execution must be prevented. Use for WpdbAtomicIdempotencyStore, lease-aware reservations, reservation_token schema migration, Idempotency-Key validation, idempotency_in_progress/conflict/replay behavior, safe stored responses, releaseOnThrowable, Woo write idempotency, or retry-safe payment/order/subscription/account operations.
    metadata:
      wp-skills-author: "Soczó Kristóf"
      wp-skills-contact: "mailto:lonsdale201@hotmail.com"
      wp-skills-plugin: "better-route"
      wp-skills-plugin-version-tested: "1.1.1"
      wp-skills-php-min: "8.1"
      wp-skills-last-updated: "2026-09-21"
    ---
    
    # better-route: atomic idempotency
    
    Use atomic idempotency for writes whose side effect must not execute twice under concurrent retries. It reserves before invoking the handler; classic `IdempotencyMiddleware` only stores after completion.
    
    ## Production setup
    
    ```php
    use BetterRoute\Middleware\Write\AtomicIdempotencyMiddleware;
    use BetterRoute\Middleware\Write\WpdbAtomicIdempotencyStore;
    
    register_activation_hook(__FILE__, static function (): void {
        (new WpdbAtomicIdempotencyStore())->installSchema();
    });
    
    $atomic = new AtomicIdempotencyMiddleware(
        store: new WpdbAtomicIdempotencyStore(),
        ttlSeconds: 86400,
        requireKey: true,
    );
    
    $router->post('/payments', $handler)
        ->protectedByMiddleware('bearerAuth')
        ->middleware([$auth, $atomic]);
    ```
    
    Run `installSchema()` for both new installs and 1.0 upgrades. The 1.1 table has a per-reservation `reservation_token`; the installer creates or migrates that column.
    
    Use `ArrayAtomicIdempotencyStore` only in tests or non-WordPress single-process checks.
    
    ## Behavior
    
    - First request reserves the canonical route + identity + client key and runs the handler.
    - Same key and fingerprint while reserved returns `409 idempotency_in_progress`.
    - Same key with a different fingerprint returns `409 idempotency_conflict`.
    - Completed identical retry replays status/body/headers and adds `Idempotency-Replayed: true`.
    - Missing key returns `400 idempotency_key_required` when required.
    - Invalid, non-printable, or overlong keys return `400 idempotency_key_invalid`; default maximum length is 200.
    
    The default fingerprint deeply canonicalizes route, method, identity, and request params. Associative key order does not change it; list order remains meaningful. Native logged-in WordPress users, auth middleware identities, and HMAC key identities scope defaults safely.
    
    ## Route scope and 1.1.1 migration
    
    Scope default keys and fingerprints to the router namespace, registered template, concrete request path and separately captured URL parameters, alongside identity and the existing payload fields. Query/body parameters must not mask URL IDs. `routePath` remains the template. Test two namespaces and two URL targets with the same client key and shadowing query/body IDs; they must remain isolated.
    
    Existing default records cannot safely translate and are not replayed through a legacy fallback. Before switching, pause writers/retries, drain requests, reconcile uncertain business operations and retire old retries across the full client retry horizon. TTL expiry alone and deleting records are insufficient. Switch all workers together; apply the same coordination on rollback. Follow `br-install-and-migrate` for the full procedure.
    
    Custom key/fingerprint resolvers own equivalent isolation. A custom key alone still uses the changed default fingerprint. After uncertain failures, reconcile before retrying with any key, even after expiry; keep business-level deduplication for irreversible effects.
    
    ## Failure semantics
    
    In 1.1 `releaseOnThrowable` defaults to `false`. A thrown exception can occur after an irreversible external side effect but before the response is recorded, so the reservation remains `in_progress` until TTL rather than allowing a dangerous automatic retry.
    
    Set `releaseOnThrowable: true` only if the complete operation is transactional/rollback-safe or the handler is known to fail before any side effect:
    
    ```php
    new AtomicIdempotencyMiddleware(
        store: $store,
        releaseOnThrowable: true,
    );
    ```
    
    Returned `WP_Error` values are not serialized and leave the reservation uncertain. They are normalized after the middleware pipeline.
    
    ## Stored response contract
    
    The wpdb store serializes a data-only envelope with `allowed_classes => false`:
    
    - arrays, scalars, and null are supported;
    - `BetterRoute\Http\Response` is decomposed into body/status/string headers;
    - `WP_REST_Response` is normalized into the same safe Response form;
    - arbitrary objects/resources inside the body are rejected.
    
    Do not return domain objects from idempotent handlers; map them to data first.
    
    Lease-aware completion and release include the unpredictable reservation token. A stale request therefore cannot complete or delete a newer reservation that reused the same key after expiry.
    
    ## WooCommerce registrar
    
    When Woo idempotency is enabled in WordPress, `WooRouteRegistrar` uses atomic idempotency across orders, products, customers, and coupons. Without a custom store it installs/migrates the wpdb store once per schema version. Installation failure is surfaced; it does not silently degrade to an array store.
    
    ```php
    'idempotency' => [
        'enabled' => true,
        'requireKey' => true,
        'ttlSeconds' => 86400,
        // 'store' => $customAtomicStore,
    ],
    ```
    
    Any custom Woo store must implement `AtomicIdempotencyStoreInterface`.
    
    ## Review checklist
    
    - Put authentication before atomic idempotency.
    - Install/migrate the wpdb schema before serving traffic.
    - Choose TTL for the longest realistic uncertain/retry window.
    - Keep the default fail-closed `releaseOnThrowable: false` unless retry safety is proven.
    - Return data-only responses.
    - Load-test two simultaneous identical requests; exactly one handler may run.
    - Test same key with a changed body and verify `idempotency_conflict`.
    - Use a custom key/fingerprint resolver only when tenant/domain scope cannot be expressed by the default identity-aware canonical form.
    
    ## Related skills
    
    - Use `br-idempotency` for lower-risk replay-cache semantics.
    - Use `br-woo-routes` for registrar integration.
    - Use `br-error-contract` for 400/409 response shapes.
    
    ## References
    
    - Verified source paths:
      - `src/Middleware/Write/AtomicIdempotencyMiddleware.php`
      - `src/Middleware/Write/WpdbAtomicIdempotencyStore.php`
      - `src/Middleware/Write/LeaseAwareAtomicIdempotencyStoreInterface.php`
      - `src/Middleware/Write/StoredResponseCodec.php`
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related