fix: enforce idempotency for change requests - #64
Conversation
Sirajmx
left a comment
There was a problem hiding this comment.
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-1is a plain test fixture intest_cannot_apply_unapproved- same
pattern asidem-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:allowcomment 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: 1over the full range; amending clears it tono leaks found.
2c2633d to
149882d
Compare
|
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
left a comment
There was a problem hiding this comment.
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.
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.
* 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.
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>
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)
Summary
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
Closes #62