Skip to content

fix: enforce idempotency for change requests - #64

Merged
Sirajmx merged 1 commit into
IABTechLab:mainfrom
garvitkaushik-123:fix/change-request-idempotency
Sep 11, 2026
Merged

Sirajmx merged 1 commit into
IABTechLab:mainfrom
garvitkaushik-123:fix/change-request-idempotency

Conversation

@garvitkaushik-123

Copy link
Copy Markdown
Contributor

Summary

  • require a non-empty idempotency key on change-request creation
  • replay the original change request for identical requests scoped to the same order
  • return the shared idempotency conflict response when a key is reused with a different payload
  • expire idempotency records after 24 hours, matching the other FD-12 endpoints

Why

POST /api/v1/change-requests was the only FD-12 money-mutating surface without replay protection. A client retry could create a second change request and place a duplicate material change into the approval queue.

The implementation follows the existing quotes/deals router pattern while preserving the current change-request payload fields. The namespace is scoped by order because this route permits optional authentication.

Testing

  • added coverage for missing keys, identical replay, changed-payload conflicts, per-order scoping, and the 24-hour TTL
  • updated existing change-request lifecycle tests for the required field
  • full suite: 1,484 passed, 28 skipped
  • changed-file lint, formatting, and whitespace checks pass

Closes #62

@Sirajmx Sirajmx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed, and the underlying fix here is solid - reviewed it against #62's replay/duplication
scenario and it holds. But the hygiene gate is genuinely red right now
(hygiene / secret scan (gitleaks, changed range)), so this can't merge as-is.

Ran gitleaks 8.24.3 locally, unredacted, against the same range CI checks
(e5b367d..2c2633d). It's a false positive, not a real leak:

Finding:  "idempotency_key": "idem-apply-unapproved-1"
RuleID:   generic-api-key    Entropy: 3.708132
File:     tests/unit/test_change_request.py:601

idem-apply-unapproved-1 is a plain test fixture in test_cannot_apply_unapproved - same
pattern as idem-minor-1, idem-material-1, etc. elsewhere in this file. It just has enough
entropy in the longer name to trip gitleaks' generic-api-key rule where the shorter ones don't.

Direction: add an inline # gitleaks:allow comment on that line. One thing worth knowing before
you push it — gitleaks scans each commit in the range individually, not a squashed diff, so
adding a new commit on top won't clear the gate; the original commit still carries the
flagged line in history. Amend the existing commit (git commit --amend) and force-push,
rather than stacking a fix commit. Verified locally both ways: a stacked commit still shows
leaks found: 1 over the full range; amending clears it to no leaks found.

@garvitkaushik-123
garvitkaushik-123 force-pushed the fix/change-request-idempotency branch from 2c2633d to 149882d Compare September 9, 2026 14:01
@garvitkaushik-123

Copy link
Copy Markdown
Contributor Author

Gitleaks false positive fixed (inline allow comment, commit amended and force-pushed). All checks are green now, including the secret scan.

This is ready to merge but needs a formal approving review. @Sirajmx, could you approve when you get a chance?

@Sirajmx Sirajmx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified: 1,463 unit + 39 integration tests passed (0 failed, matching CI exactly). Live E2E also passed - identical replays return the same record, while reusing a key with a different body correctly returns 409. Merging.

@Sirajmx
Sirajmx merged commit 7131403 into IABTechLab:main Sep 11, 2026
5 checks passed
garvitkaushik-123 added a commit to garvitkaushik-123/seller-agent that referenced this pull request Sep 14, 2026
PR IABTechLab#64 (merged after this branch opened) made idempotency_key a
required field on POST /api/v1/change-requests. Without it, the
request in test_existing_order_with_null_deal_id_returns_structured_error
now gets rejected at validation (422) before it ever reaches the
deal_id_required check this test exists to verify.

Caught by Sirajmx on review -- confirmed against a local merge of both
branches before pushing.
Sirajmx pushed a commit that referenced this pull request Sep 15, 2026
* fix: reject change requests for dealless orders

* test: add required idempotency_key to dealless change-request test

PR #64 (merged after this branch opened) made idempotency_key a
required field on POST /api/v1/change-requests. Without it, the
request in test_existing_order_with_null_deal_id_returns_structured_error
now gets rejected at validation (422) before it ever reaches the
deal_id_required check this test exists to verify.

Caught by Sirajmx on review -- confirmed against a local merge of both
branches before pushing.
numarasSigmaSoftware added a commit to numarasSigmaSoftware/seller-agent that referenced this pull request Sep 15, 2026
The committed OpenAPI document had fallen behind the application.
Regenerated with scripts/generate_openapi.py; no code change.

What the regeneration picks up:
- the operator key route POST /auth/api-keys/operator and its request
  schema (IABTechLab#59)
- operator-auth header parameters on 17 admin paths
  (IABTechLab#39)
- description-only updates on 7 paths, several of them buyer-facing:
  quotes, deals, change requests, negotiation messages, order transition
- the required idempotency_key on change-request creation
  (IABTechLab#64) and on quotes (IABTechLab#50)
- exclusiveMinimum on RateCardEntry.base_cpm (IABTechLab#78)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
numarasSigmaSoftware added a commit to numarasSigmaSoftware/seller-agent that referenced this pull request Sep 15, 2026
The committed OpenAPI document had fallen behind the application.
Regenerated with scripts/generate_openapi.py; no code change.

What the regeneration picks up:
- the operator key route POST /auth/api-keys/operator and its request
  schema (IABTechLab#59)
- operator-auth header parameters on 17 admin paths
  (IABTechLab#39)
- description-only updates on 7 paths, several of them buyer-facing:
  quotes, deals, change requests, negotiation messages, order transition
- the required idempotency_key on change-request creation
  (IABTechLab#64) and on quotes (IABTechLab#50)
- exclusiveMinimum on RateCardEntry.base_cpm (IABTechLab#78)
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.

POST /api/v1/change-requests accepts no idempotency_key and creates duplicate change requests on replay

2 participants