feat: publish deal.created from every booking path - #85
Open
numarasSigmaSoftware wants to merge 8 commits into
Open
numarasSigmaSoftware wants to merge 8 commits into
numarasSigmaSoftware wants to merge 8 commits into
Conversation
Only the approval flow and the negotiation service emitted events. A deal booked over REST (quote -> book, from-template, or curated) left GET /events empty, so a client polling the feed never learned a deal existed. Add _emit_deal_created() and call it after each booking path persists its deal. The payload carries source (quote | template | curated), deal_type, status, product_id and quote_id. Fail-open like every non-audit event: a bus failure never fails the booking. Tests authenticate the quote booking call, since IABTechLab#77 requires a verified buyer on POST /api/v1/deals.
…nches _emit_deal_created is fail-closed, not fail-open: deal.created is in AUDIT_EVENT_TYPES, so a bus failure writes the event to the audit fallback file and the booking still succeeds; if that write also fails, the error propagates after the deal is already persisted. No code-path change. Two tests pin the branches over HTTP on the quote path.
bulk_deal_operations (create branch) and migrate_deal persisted a new deal without publishing deal.created, so the event feed missed those bookings. Both now call _emit_deal_created right after storage.set_deal, with source "bulk" and "migration"; the helper docstring lists all five sources. No emission for deprecate or for the old deal in a migration.
Same quote booked twice over HTTP with the same idempotency_key: both responses are 200 with the same deal_id and the bus holds exactly one deal.created. Test-only; the emission already sits after the replay short-circuit.
Each payload-inspecting test now asserts source, deal_type, status, product_id and quote_id (None where the path books without a quote) through one module-level _assert_payload helper shared by the five tests.
The http_client fixture's _FakeStorage has no ``set``, so the booking's audit-class deal.created fell back to data/audit_fallback.jsonl on every run. The fixture now patches get_event_bus to an InMemoryEventBus for every test that books through it, and the fallback path is gitignored next to *.db.
The DEAL_CREATED row said the event came from an accepted proposal; it is published by every booking path, with payload.source naming which one.
This was referenced Sep 15, 2026
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.
Problem
Only the approval flow and the negotiation service publish events. A deal booked over REST left
GET /eventsempty, so a client polling the feed never learned a deal existed. Verified on a running instance: two bookings, event count zero.Change
Add
_emit_deal_created()and call it after each booking path persists its deal: quote booking, from-template, curated, bulk create, and migration. The payload carriessource(one of those five),deal_type,status,product_id, andquote_id.deal.createdis an audit-class event in the event model, so the emission is fail-closed by design: if the bus fails, the event is written to the audit fallback log and the booking still returns 200; if that write also fails, the error propagates after the deal is already persisted. Both branches are pinned by tests. The retry-after-failure ordering, where a failure after the quote is flipped leaves a retry with a 409 and no deal id, applies to any post-persist failure and predates this change; it is tracked as #88.Docs: the event-bus overview now describes the real emitters and the
sourcevalues; changelog entry under Unreleased;data/audit_fallback.jsonlis gitignored. The fallback file is the same class of leftover as the database in #82: the audience-plan tests wrote it through a fake storage that backed the event bus, and now publish to an in-memory bus instead.Verification
tests/unit/test_booking_events.py: one test per booking path (five), bus failure with fallback written and booking at 200, fallback failure propagating after persist, idempotent replay emitting exactly once, and every payload asserted on all five keys. Each behavior test was seen failing before its change.tests/unit/test_audience_plan_validation.pynow publishes to an in-memory bus, so its fake storage no longer trips the audit fallback; a suite run on head leaves the tree clean.POST /api/v1/deals.test_self_asserted_advertiser_identity_is_floored, because the suite leavesad_seller.dbbehind; that is Unit suite leaves ./ad_seller.db behind, so a second consecutive local run can fail #82 and is not touched by this PR.Independent of #84; the
deal_service.pyhunks are disjoint.Not changed
From-template booking has no idempotency lookup, so a retried request creates a second deal and now a second event. Pre-existing; tracked as #87.