Skip to content

proposal_finalize storyboard: brief → create_media_buy has no path to COMMITTED state #723

Description

@bokelley

Summary

The media_buy_seller/proposal_finalize/create_media_buy storyboard step goes directly from get_products(buying_mode=brief) to create_media_buy(proposal_id=X) with no intermediate step. But proposal_dispatch.maybe_hydrate_recipes_for_create_media_buy calls store.try_reserve_consumption(), which requires the proposal to be in COMMITTED state — and the only framework path that promotes DRAFTCOMMITTED is maybe_intercept_finalize, which requires the manager to declare capabilities.finalize=True AND implement finalize_proposal().

A manager that only declares capabilities.refine=True (the v1 shape most adopters ship) has no path to advance a proposal past DRAFT. Result: the storyboard cannot pass without one of:

  1. A second storyboard step the buyer must call between get_products and create_media_buy (e.g. get_products(buying_mode=refine, refine=[{action: finalize, proposal_id: X}]))
  2. A finalize-capable manager (most adopters don't have one yet — finalize is a richer surface)
  3. An adopter workaround that auto-commits at put_draft time (what we shipped)

The storyboard as written suggests option 1 was the intent, but the step sequence in proposal_finalize/create_media_buy doesn't include a finalize call — buyers go straight from brief to create_media_buy.

Repro

Wire LazyPlatformRouter (with workaround from #722) + a ProposalStore Protocol impl + a ProposalManager that declares only refine=True. Run the proposal_finalize/create_media_buy storyboard:

  1. get_products(buying_mode=brief) → manager returns proposals[] → framework calls store.put_draft() → record is DRAFT
  2. create_media_buy(proposal_id=X) → framework calls store.try_reserve_consumption(X) → state is DRAFT, not COMMITTEDAdcpError("PROPOSAL_NOT_COMMITTED")

Where we landed (workaround)

In our SalesAgentProposalStore.put_draft, we write state=COMMITTED directly with a 7-day expires_at (salesagent#390, src/core/database/repositories/proposal_store.py). The Protocol surface is unchanged — only the internal lifecycle state differs. But this means every adopter wiring a ProposalStore against the current storyboard set hits the same lifecycle gap and writes the same workaround.

Proposed paths forward

Option A — add a finalize step to the storyboard. Most spec-honest: the buyer explicitly transitions the proposal from DRAFT to COMMITTED via a finalize call. Requires the storyboard to include the intermediate step and every adopter to ship a finalize_proposal method on their manager.

Option B — manager capability auto_commit_on_put_draft: bool = False. Lets adopters opt into the simpler flow where get_products issues directly-consumable proposals. Framework reads the capability and either (a) writes COMMITTED on put_draft when the capability is True, or (b) issues a synthetic commit() call inline. Adopters who want canonical DRAFT semantics leave it False.

Option C — split storyboard into two flavors. One walks the full DRAFT → finalize → COMMITTED → consumption lifecycle; the other (matching today's proposal_finalize/create_media_buy step sequence) explicitly documents that proposals issued via brief mode are committed-on-issuance.

Option D — clarify in docs that the v1 path requires the adopter to manage commit themselves. Lightest touch — acknowledges what every adopter is already doing.

We'd recommend B or C. The current shape leaves adopters discovering the lifecycle compromise by hitting PROPOSAL_NOT_COMMITTED and reading proposal_dispatch source to figure out why.

Context

Hit while implementing #387 against the salesagent. Adopter workaround documented in salesagent PR #390. Filed concurrent issue #722 on the LazyPlatformRouter parity gap.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions