Skip to content

epic-2/story-4: Expose Tracking Lifecycle Events and Delivery Outcomes - #32

Closed
usmanabbas7 wants to merge 2 commits into
epic-2/story-3-batch-deduplicate-and-flush-tracking-eventsfrom
epic-2/story-4-expose-tracking-lifecycle-events-and-delivery-outcomes
Closed

epic-2/story-4: Expose Tracking Lifecycle Events and Delivery Outcomes#32
usmanabbas7 wants to merge 2 commits into
epic-2/story-3-batch-deduplicate-and-flush-tracking-eventsfrom
epic-2/story-4-expose-tracking-lifecycle-events-and-delivery-outcomes

Conversation

@usmanabbas7

Copy link
Copy Markdown
Collaborator

Story 2.4 — Expose Tracking Lifecycle Events and Delivery Outcomes

Adds the observability surface on top of Story 2.3's queue/dedup/flush/transport layer: lifecycle-event emission and delivery-outcome reporting. Integrates with — does not re-implement — the existing tracking pipeline.

What was built

  • events.py (L0)LifecycleEvent enum (READY/CONFIG_UPDATED/BUCKETING/CONVERSION/API_QUEUE_RELEASED/DATA_STORE_QUEUE_RELEASED, JS-parity dot-separated values) + typed event payload structures. Runtime-clean L0 (references ReleaseReason only under TYPE_CHECKING).
  • ports/event_bus.py (L1)EventBus typing.Protocol (on + emit).
  • adapters/events/in_process.py (L3) — in-process synchronous EventBus; per-listener try/except isolates, logs, and swallows handler errors (JS EventManager.fire parity); no-subscriber emit is a zero-cost no-op.
  • Core.on(event, handler) (L4) — public subscription surface; one EventBus per Core, injected into the tracker. LifecycleEvent exported from __init__.py.
  • Emission call sitesCONVERSION from the tracker only on a tracked (non-suppressed) enqueue; API_QUEUE_RELEASED once per release on the single shared release path with success/failure outcome + privacy-safe logging.

Acceptance criteria & precedence rulings

All 5 ACs and 12 Critical Warnings satisfied. Sprint-driver precedence rulings honored (resolved against current PRD ground truth):

  • F-003/F-004 — enum member is API_QUEUE_RELEASED = "api.queue.released", CONFIG_UPDATED = "config.updated" (per audit-corrected prd.md:398-404; stale QUEUE_RELEASED/config_updated prose ignored).
  • F-010 — Python failure path drops events, emits API_QUEUE_RELEASED with error, does NOT restart timer/re-queue; flush() stays non-raising (intentional divergence from JS).
  • F-062 — reuses Story 2.3's ReleaseReason enum; no second release-reason representation.

Tests

340 → 366 (+26; all passing offline via the qs-06 RESPX harness). Two stale Story 2.3 tests asserting preserve-and-raise on delivery failure were updated to the authoritative drop-and-don't-raise contract (F-010).

Traceability

Beads epic ai-driven-product-dev-v7zj; tasks -vqmn, -d3l7, -37ep, -w34z, -orob, -9y97, -a6iv (all closed).

Readiness gate: PASS 9.1/10, no auto-delegated questions. Code review: clean, round 1, no warnings.

Part of sprint sprint/2026-04-06-convert-python-sdk. Stacked on #31 (story 2-3) → #30#29#24 chain.

@usmanabbas7 usmanabbas7 self-assigned this Jun 8, 2026
@usmanabbas7
usmanabbas7 requested a review from clllaur June 8, 2026 07:23

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a lifecycle event model and an in-process synchronous event bus to the Convert Python SDK, allowing consumers to subscribe to SDK events like conversions and queue releases. It integrates event emission into the tracking queue release path, updates the delivery failure contract to drop events rather than re-queue them, and adds extensive tests. Feedback on the changes suggests moving payload serialization and transport resolution inside the try-except block in the release path to prevent event loss on non-delivery faults, and tightening the EventHandler type signature to enable static type checking for registered handlers.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 267 to 270
payload = self._build_batch_payload(items)
transport = self._ensure_transport()
try:
transport.send_tracking(payload, sdk_key=str(self._config.sdk_key))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The _build_batch_payload(items) and _ensure_transport() calls are executed outside of the try/except block. If a serialization bug or transport-provisioning error occurs (which are non-delivery faults), the exception will propagate immediately without executing the except Exception: block on line 292. As a result, the drained events will be permanently lost instead of being restored to the queue as intended by the design contract.

Moving these calls inside the try block ensures that any non-delivery faults during payload construction or transport resolution will correctly trigger the queue restoration before propagating the error.

Suggested change
payload = self._build_batch_payload(items)
transport = self._ensure_transport()
try:
transport.send_tracking(payload, sdk_key=str(self._config.sdk_key))
try:
payload = self._build_batch_payload(items)
transport = self._ensure_transport()
transport.send_tracking(payload, sdk_key=str(self._config.sdk_key))

#: A lifecycle-event handler. Invoked as ``handler(payload, error)`` — mirroring
#: the JS ``EventManager`` ``fn(args, err)`` contract — so a handler can observe
#: both the success payload and an optional delivery error.
EventHandler = Callable[..., None]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using Callable[..., None] as the type alias for EventHandler disables static type checking for the handler's signature. Since the SDK strictly invokes handlers with two arguments (payload and error), registering a handler with a different signature (e.g., a single-argument lambda like lambda payload: ...) will pass static analysis but raise a TypeError at runtime, which is then silently swallowed and logged as an error by the event bus.

Changing the type definition to Callable[[Any, Optional[BaseException]], None] allows static type checkers (like mypy or pyright) and IDEs to enforce and surface the correct signature to integrators.

Suggested change
EventHandler = Callable[..., None]
EventHandler = Callable[[Any, Optional[BaseException]], None]

usmanabbas7 and others added 2 commits June 14, 2026 21:58
Beads: ai-driven-product-dev-vqmn ai-driven-product-dev-d3l7 ai-driven-product-dev-37ep ai-driven-product-dev-w34z

L0 events.py: LifecycleEvent enum (PRD-frozen identifiers incl API_QUEUE_RELEASED)
+ typed ConversionEventPayload / QueueReleasedPayload (privacy-safe, ReleaseReason reused).
L1 ports/event_bus.py: EventBus Protocol (on/emit).
L3 adapters/events/in_process.py: synchronous bus, per-handler try/except isolation, no-op when no subscribers.
logging.py: privacy-safe queue-release / delivery-failure / handler-error log sites.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Beads: ai-driven-product-dev-orob ai-driven-product-dev-9y97 ai-driven-product-dev-a6iv

- Core.on(event,handler): one EventBus per Core, injected into Tracker; export LifecycleEvent.
- Tracker emits CONVERSION only after a tracked (non-suppressed) enqueue; never on dedup/goal-not-found.
- Single shared release path emits API_QUEUE_RELEASED once per actual release with reason+batch_size+counts;
  empty flush emits nothing. Per-trigger reason (explicit/size/timeout/atexit) via flush_timeout/flush_atexit.
- F-010: on TrackingDeliveryError emit failure outcome (status_code+retry only), log privacy-safe, DROP events,
  do NOT raise (flush() stays non-raising). Updated stale Story 2.3 preserve-and-raise tests to F-010 contract.
- New: tests/test_lifecycle_events.py; extended conversion/delivery/queue-lifecycle suites. 366 passed (was 340).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@usmanabbas7
usmanabbas7 force-pushed the epic-2/story-3-batch-deduplicate-and-flush-tracking-events branch from fb7d7f5 to 627d3ca Compare June 14, 2026 16:58
@usmanabbas7
usmanabbas7 force-pushed the epic-2/story-4-expose-tracking-lifecycle-events-and-delivery-outcomes branch from a4d2036 to 919e549 Compare June 14, 2026 16:59
@abbaseya

Copy link
Copy Markdown
Collaborator

Superseded — all commits already in main (bc76b64). Closing without merge as part of post-sprint cleanup.

@abbaseya abbaseya closed this Jun 18, 2026
@abbaseya
abbaseya deleted the epic-2/story-4-expose-tracking-lifecycle-events-and-delivery-outcomes branch June 18, 2026 16:31
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.

2 participants