Motivation
Two new error codes needed for the proposal lifecycle (`buying_mode='refine'` with `action='finalize'`):
- `PROPOSAL_EXPIRED` — `create_media_buy(proposal_id=...)` issued after the committed proposal's `expires_at` hold window has passed. Buyer-fixable: the buyer must re-finalize a fresh proposal. Non-transient.
- `PROPOSAL_NOT_FOUND` — `create_media_buy` references a `proposal_id` the seller doesn't recognize (typo, wrong tenant, evicted from cache before consumption). Buyer-fixable: re-issue `get_products` to obtain a current proposal.
These are the natural counterparts to existing codes like `MEDIA_BUY_NOT_FOUND`. Today there's no spec-conformant way to signal proposal-lifecycle errors — adopters either reuse `INVALID_REQUEST` (loses semantics, recovery type wrong) or invent local codes (no cross-SDK consistency).
Proposed semantics
`PROPOSAL_EXPIRED`
```yaml
PROPOSAL_EXPIRED:
recovery: terminal
category: proposal-lifecycle
description: |
The proposal's expires_at hold window has passed. Buyer must
re-issue get_products with buying_mode='refine' + action='finalize'
to obtain a fresh committed proposal before calling create_media_buy.
examples:
- "create_media_buy(proposal_id='abc') called 2 hours after the proposal's
expires_at='2026-04-15T14:00:00Z' window expired"
```
`PROPOSAL_NOT_FOUND`
```yaml
PROPOSAL_NOT_FOUND:
recovery: correctable
category: proposal-lifecycle
description: |
The proposal_id is not recognized by the seller — never finalized,
belongs to a different tenant, or evicted from the seller's session
cache. Buyer should re-issue get_products to obtain a current
proposal_id.
examples:
- "create_media_buy(proposal_id='unknown_xyz') without a prior finalize"
- "proposal evicted from session cache before create_media_buy"
```
Wire shape
Same envelope as every other error. Just two new code values in the catalog.
Why now
The Python SDK is shipping v1.5 ProposalManager (PR #538 design doc; implementation pending) which includes finalize-transition handling and `expires_at` enforcement. The framework needs spec-conformant codes for the structured error envelope (especially after PR #525 + PR #536 made structured error codes load-bearing for storyboard `/adcp_error/code` JSON-pointer assertions).
The Python SDK will ship these codes via its `KNOWN_NON_SPEC_CODES` allowlist as a stopgap (same pattern as `CONFIGURATION_ERROR` from spec issue #3995) until the spec catalog includes them. Other SDKs implementing proposal lifecycle support will face the same gap.
Acceptance
Refs
Motivation
Two new error codes needed for the proposal lifecycle (`buying_mode='refine'` with `action='finalize'`):
These are the natural counterparts to existing codes like `MEDIA_BUY_NOT_FOUND`. Today there's no spec-conformant way to signal proposal-lifecycle errors — adopters either reuse `INVALID_REQUEST` (loses semantics, recovery type wrong) or invent local codes (no cross-SDK consistency).
Proposed semantics
`PROPOSAL_EXPIRED`
```yaml
PROPOSAL_EXPIRED:
recovery: terminal
category: proposal-lifecycle
description: |
The proposal's expires_at hold window has passed. Buyer must
re-issue get_products with buying_mode='refine' + action='finalize'
to obtain a fresh committed proposal before calling create_media_buy.
examples:
- "create_media_buy(proposal_id='abc') called 2 hours after the proposal's
expires_at='2026-04-15T14:00:00Z' window expired"
```
`PROPOSAL_NOT_FOUND`
```yaml
PROPOSAL_NOT_FOUND:
recovery: correctable
category: proposal-lifecycle
description: |
The proposal_id is not recognized by the seller — never finalized,
belongs to a different tenant, or evicted from the seller's session
cache. Buyer should re-issue get_products to obtain a current
proposal_id.
examples:
- "create_media_buy(proposal_id='unknown_xyz') without a prior finalize"
- "proposal evicted from session cache before create_media_buy"
```
Wire shape
Same envelope as every other error. Just two new code values in the catalog.
Why now
The Python SDK is shipping v1.5 ProposalManager (PR #538 design doc; implementation pending) which includes finalize-transition handling and `expires_at` enforcement. The framework needs spec-conformant codes for the structured error envelope (especially after PR #525 + PR #536 made structured error codes load-bearing for storyboard `/adcp_error/code` JSON-pointer assertions).
The Python SDK will ship these codes via its `KNOWN_NON_SPEC_CODES` allowlist as a stopgap (same pattern as `CONFIGURATION_ERROR` from spec issue #3995) until the spec catalog includes them. Other SDKs implementing proposal lifecycle support will face the same gap.
Acceptance
Refs