Skip to content

feat: publish deal.created from every booking path - #85

Merged
Kishore-2572 merged 11 commits into
IABTechLab:mainfrom
numarasSigmaSoftware:feature/booking-events
Oct 7, 2026
Merged

Kishore-2572 merged 11 commits into
IABTechLab:mainfrom
numarasSigmaSoftware:feature/booking-events

Conversation

@numarasSigmaSoftware

@numarasSigmaSoftware numarasSigmaSoftware commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Only the approval flow and the negotiation service publish events. A deal booked over REST left GET /events empty, 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 carries source (one of those five), deal_type, status, product_id, and quote_id.

deal.created is 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 source values; changelog entry under Unreleased; data/audit_fallback.jsonl is 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.py now 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.
  • The quote booking test authenticates as a buyer, since fix: booking accepted anonymous callers - require verified buyer matching the quote #77 requires a verified buyer on POST /api/v1/deals.
  • Full unit suite on a fresh database: all pass. On a reused working directory one pre-existing test fails, test_self_asserted_advertiser_identity_is_floored, because the suite leaves ad_seller.db behind; 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.py hunks 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.

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.
@numarasSigmaSoftware

Copy link
Copy Markdown
Contributor Author

@atc964 @Sirajmx Can you take a look on this PR? I can provided further info if needed.

@aleksUIX

aleksUIX commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

In bulk create, deal.created is published before the quote is marked booked. A listener can read an available quote for an already-created deal. Can the emit move after set_quote?

numarasSigmaSoftware and others added 2 commits October 5, 2026 13:44
Bulk create published before marking the quote booked, so a listener
could read an available quote for a deal that already existed.
Migration had the same shape: it published before deprecating the
replaced deal. Both now publish last; the tests record what a
listener reads from storage at publish time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	src/ad_seller/services/deal_service.py
@numarasSigmaSoftware

Copy link
Copy Markdown
Contributor Author

Good catch, fixed in 4577058. Bulk create now publishes deal.created after the quote is marked booked. Migration had the same ordering: it published before deprecating the replaced deal, so it now publishes last too. The bulk and migration tests now record what a listener reads from storage at publish time (quote booked, replaced deal deprecated); both failed before the change. Quote, template and curated already emitted after their last write.

@Kishore-2572 Kishore-2572 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.

Both of Aleks's and the author's own fixes verified live, not just read. Reproduced the original defect on the pre-fix tree first: bulk_deal_operations genuinely published deal.created before storage.set_quote(booked), so a listener at publish time would see an available quote for a deal that already existed; migrate_deal had the identical shape, publishing before the replaced deal was marked deprecated.

Verified the fix (4577058) closes both, and checked it isn't scoped narrower than it needs to be: traced all 5 deal-creation paths (quote, template, curated, bulk, migration) and every one now emits strictly after its path's last write. Checked every other storage.set_deal() call in the file (lazy expiry, bulk cancel, bulk notes-update, standalone deprecate) — all updates to already-existing deals, correctly not re-emitting. No sixth creation path silently skips the event.

Negative control, run myself: reverted just the source fix with the test file untouched, reran test_booking_events.py — exactly the 2 tests covering bulk/migration failed, the other 6 stayed green. Confirms the new tests actually discriminate against the regression.

Cross-repo: grepped buyer-agent for any consumption of deal.created — none. Seller-side-only audit feed, zero cross-repo risk.

Full suite green: 1664 passed (0 failed) on the latest rebase onto current main, ruff check clean, no conflicts anywhere.

@Kishore-2572
Kishore-2572 merged commit 81a2e73 into IABTechLab:main Oct 7, 2026
5 checks passed
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.

3 participants