Skip to content

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

@bokelley

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions