Skip to content

fix: harden media buy response normalization#846

Merged
bokelley merged 1 commit into
mainfrom
bokelley/media-buy-response-hardening
May 24, 2026
Merged

fix: harden media buy response normalization#846
bokelley merged 1 commit into
mainfrom
bokelley/media-buy-response-hardening

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 24, 2026

Summary

Follow-up to #842 for the non-blocking Argus cleanup items that were worth doing now.

  • centralize legacy media-buy lifecycle status inference in one helper
  • stop the MCP boundary from treating draft, cancelled, or ambiguous completed as inferred media_buy_status
  • avoid stamping task-like responses that already contain task_id as status="completed"
  • keep UpdateMediaBuyResponse3 public for source compatibility while preserving UpdateMediaBuySubmittedResponse as the semantic alias

Validation

  • PYTHONPATH=src python3 -m ruff check src/adcp/types/media_buy_status_helpers.py src/adcp/types/aliases.py src/adcp/types/__init__.py src/adcp/__init__.py src/adcp/types/generated_poc/media_buy/create_media_buy_response.py src/adcp/types/generated_poc/media_buy/update_media_buy_response.py scripts/post_generate_fixes.py tests/test_server_dx.py tests/test_schema_validation_server.py
  • PYTHONPATH=src python3 -m pytest tests/test_schema_validation_server.py tests/test_server_dx.py tests/test_import_layering.py tests/test_public_api.py tests/test_create_media_buy_response_types.py tests/test_type_guards.py tests/test_code_generation.py -q
  • PYTHONPATH=src python3 -m pytest tests/test_schema_validation.py tests/test_protocols.py -q
  • PYTHONPATH=src python3 -m mypy src/adcp/
  • PYTHONPATH=src python3 -m mypy --strict tests/type_checks/
  • python3 scripts/post_generate_fixes.py
  • pre-commit on commit: black, ruff, mypy, bandit, whitespace/EOF/JSON/large-file/conflict/key checks

@bokelley bokelley force-pushed the bokelley/media-buy-response-hardening branch from d6f69ef to 94ba8e9 Compare May 24, 2026 12:32
@bokelley bokelley force-pushed the bokelley/media-buy-response-hardening branch from 94ba8e9 to 532459a Compare May 24, 2026 12:54
@bokelley bokelley marked this pull request as ready for review May 24, 2026 12:59
Copy link
Copy Markdown

@aao-ipr-bot aao-ipr-bot Bot left a comment

Choose a reason for hiding this comment

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

Clean follow-up. Centralizes the legacy lifecycle inference set in one place and aligns the dict-based MCP boundary with the typed-validator behavior — coherence win.

Things I checked

  • MEDIA_BUY_LEGACY_STATUS_VALUES at src/adcp/types/media_buy_status_helpers.py:13-22 matches the upstream MediaBuyStatus enum minus completed. Dropping draft is correct (not in the upstream enum at all). Dropping British cancelled is correct (upstream uses American canceled only).
  • _normalize_response_envelope at src/adcp/server/mcp_tools.py:67-71 collapses to the right shape now that completed is no longer in the legacy set — the prior inner if raw_status != "completed": guard is semantically equivalent to the new code, but only because the frozenset excludes it. Worth a code-side comment so a future re-add doesn't quietly break it.
  • Generated-file regeneration audit: scripts/post_generate_fixes.py:1515-1537 media_header / update_media_header produce exactly what landed in src/adcp/types/generated_poc/media_buy/create_media_buy_response.py and update_media_buy_response.py. The Sequence import shows up only in the update variant, as expected.
  • Layering: new media_buy_status_helpers.py has zero generated_poc/ / _generated imports; mcp_tools.py reaches it through adcp.types (public surface). Layering rule holds.
  • Validator branch ordering at create_media_buy_response.py:35-53 / update_media_buy_response.py:37-55 has no fall-through bugs; each arm copies before mutating.
  • New elif raw_status == "completed": arm is the enum→string normalizer for MediaBuyStatus.completed constructor inputs. test_typed_success_does_not_infer_completed_lifecycle at tests/test_server_dx.py:189-194 and :248-251 exercises it.
  • UpdateMediaBuyResponse3 re-export at src/adcp/types/aliases.py:1880 is non-breaking — already in src/adcp/__init__.py.__all__ per git show HEAD~. Intermediate layer now matches.

Follow-ups (non-blocking — file as issues)

  • Warn-mode wire-conformance gap. ad-tech-protocol-expert flagged two: (a) {"task_id": ...} with no status now falls through to a status-less response (mcp_tools.py:73-74), and (b) handler-returned status="draft" ships unchanged in warn mode (tests/test_schema_validation_server.py:278-298 codifies this). The previous behavior was also wrong (synthetic completed stamp, or rewriting draft into media_buy_status), but both flavors leave the MCP boundary willing to emit a status that isn't in TaskStatus. Decide the contract: either default status="submitted" when task_id is present, or fail in both modes on a non-TaskStatus value. Same question for British cancelled — file a small follow-up if a tolerance window is intended.
  • Default-status loss for non-media-buy methods. mcp_tools.py:73 changed from result.setdefault("status", "completed") (global default) to a guarded form. The guard targets the task-envelope case, but any future tool returning {"task_id": ..., "status": <something>} with status accidentally omitted will silently miss the completed default. Worth a one-line comment scoping the guard's intent.
  • Changelog note for the behavior change. Adopters returning status="draft" or status="cancelled" (British) previously got auto-normalized; they now hit validation. The PR description covers this — the released changeset should too so adopters know to fix their handlers.

Minor nits (non-blocking)

  1. Comment the "no-op" completed branch. create_media_buy_response.py:43-45, update_media_buy_response.py:45-47, and the mirror sites in scripts/post_generate_fixes.py:1935-1937 / :2008-2010elif raw_status == "completed": data["status"] = "completed" reads as a no-op until you remember it's the MediaBuyStatus.completed enum→string coercion. One-line comment saves the next reader an audit.
  2. __all__ re-export style. src/adcp/types/__init__.py:790-797 uses as NAME for MEDIA_BUY_LEGACY_STATUS_VALUES but not for unwrap_enum_value. Pick one — the explicit-re-export idiom for type checkers is to use as NAME on both.
  3. Test-only. tests/test_schema_validation_server.py:278-298 confirms the draft payload survives unmodified but doesn't assert the validation warning was actually logged. A caplog assertion or a strict-mode counterpart would lock in the "validation is the enforcement point" contract.

LGTM. Follow-ups noted.

@bokelley bokelley merged commit 5defa15 into main May 24, 2026
24 checks passed
@bokelley bokelley deleted the bokelley/media-buy-response-hardening branch May 24, 2026 13:09
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.

1 participant