feat(decisioning): ProposalCapabilities.auto_commit_on_put_draft (#723)#725
Merged
Conversation
Closes #723 (option B). Pre-#723 the storyboard ``media_buy_seller/proposal_finalize/create_media_buy`` was unreachable for managers that didn't declare ``finalize=True``: brief mode returned proposals as DRAFT, next call's ``try_reserve_consumption`` raised ``PROPOSAL_NOT_COMMITTED``, and the only framework path that promoted DRAFT → COMMITTED was ``finalize_proposal`` (which most v1 adopters didn't ship). Adopters worked around it by writing ``state=COMMITTED`` directly inside their ``put_draft`` implementations (salesagent PR #390). This adds the framework-side equivalent. When a manager declares ``auto_commit_on_put_draft=True``, the dispatcher calls ``ProposalStore.commit`` immediately after ``put_draft`` on every proposal returned by ``get_products`` / ``refine_products``, promoting DRAFT → COMMITTED in a single dispatch. New ProposalCapabilities fields: - ``auto_commit_on_put_draft: bool = False`` - ``auto_commit_ttl_seconds: int = 604800`` (7 days, salesagent default) Construction-time validation: - auto_commit + finalize mutually exclusive (both on would race). - ``auto_commit_ttl_seconds <= 0`` rejected (would expire on commit). Tests: 7 new in tests/test_proposal_auto_commit.py. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…725) - Loud-fail ``auto_commit_on_put_draft=True`` on ``sales-guaranteed`` specialism. Product-expert catch: a 7-day default TTL inventory hold issued silently from every ``get_products`` call would burn guaranteed-direct inventory across thousands of catalog probes per day. GAM / ad-server proposal lifecycles require explicit buyer-driven reservation precisely because trafficking ops won't accept silent holds. Loud-fail with a clear migration path (switch to ``sales-non-guaranteed`` or set ``finalize=True``). - Implement the >30-day TTL soft-cap warning the docstring promised. The framework permits longer TTLs (long-running RFPs legitimately need them) but warns at boot so the choice is visible at declaration time. Boundary check pins that exactly 30 days does not trigger. - Move ``from datetime import timedelta`` to the module-level import (was inline inside the per-proposal loop). - 3 new tests: ``sales-guaranteed`` rejection, >30d warning + 30d boundary, catalog-mode (store wired but manager unwired stays in DRAFT regardless). - Rename local variable ``_SOFT_CAP_SECONDS`` → ``_soft_cap_seconds`` per ruff N806 (function-local should be lowercase). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Closes #723 (option B from the issue).
Summary
Pre-#723 the storyboard step
media_buy_seller/proposal_finalize/create_media_buywas unreachable for managers that didn't declarefinalize=True— brief mode returned proposals as DRAFT, next call'stry_reserve_consumptionraisedPROPOSAL_NOT_COMMITTED. Adopters wrotestate=COMMITTEDdirectly inside theirput_draftimplementations (salesagent PR #390); this PR ships the framework-side equivalent.Surface
When
auto_commit_on_put_draft=True, the framework callsProposalStore.commitimmediately afterput_drafton every proposal returned, promoting DRAFT → COMMITTED in a single dispatch.Validation
auto_commit_on_put_draft=True+finalize=True→AdcpError. Mutually exclusive — both on would race.auto_commit_ttl_seconds <= 0→AdcpError. Zero would expire on commit.Why option B (not A, C, D from the issue)
finalize_proposal.Adopter migration
Salesagent drops the inline
state=COMMITTEDinSalesAgentProposalStore.put_draftand setsauto_commit_on_put_draft=Trueon itsProposalCapabilities.Test plan
ruff+mypyclean.🤖 Generated with Claude Code