Module adcp.canonical_formats.advisory
SDK-source errors[] advisory construction.
The canonical-formats projection emits non-fatal advisories on the
errors[] array of get_products / list_creative_formats
responses. Advisories carry source="sdk" (vs. seller-emitted
producer entries) and sdk_id="adcontextprotocol-adcp-python@<version>"
so multi-hop consumers can attribute the entry to this SDK and
deduplicate on (code, field) per the multi-hop propagation contract
in core/error.json.
The advisory functions live separately from :mod:adcp.canonical_formats.v2_to_v1
so other SDK paths (e.g., the v1→v2 reverse projection in a future PR,
the closed-set validator's own dispatch path) can emit the same shape
without circular imports.
Functions
def make_sdk_advisory(*,
code: str,
message: str,
field: str | None = None,
details: dict[str, Any] | None = None,
recovery: Recovery = correctable,
suggestion: str | None = None) ‑> adcp.types.generated_poc.core.error.Error-
Expand source code
def make_sdk_advisory( *, code: str, message: str, field: str | None = None, details: dict[str, Any] | None = None, recovery: Recovery = Recovery.correctable, suggestion: str | None = None, ) -> Error: """Build an SDK-source advisory entry for ``errors[]`` augmentation. Sets ``source=sdk`` and ``sdk_id=<package>@<version>`` per the multi-hop propagation contract in ``core/error.json``. Consumers receiving this entry MUST treat it as advisory — the response stays success on the v1 path; only the v2 projection is degraded. Args: code: AdCP error code (e.g., ``FORMAT_DECLARATION_V1_AMBIGUOUS``). Must be ≤64 chars per the wire schema. message: Human-readable description. field: JSONPath-lite pointer to the offending field (e.g., ``products[0].format_options[2]``). details: Code-specific structured payload. recovery: Recovery classification — defaults to ``correctable`` because canonical-projection advisories tell the seller what to fix (add ``v1_format_ref``, file a registry PR, etc.). suggestion: Optional one-line fix hint surfaced to operators. """ return Error( code=code, message=message, field=field, details=details, recovery=recovery, source=Source.sdk, sdk_id=_resolve_sdk_id(), suggestion=suggestion, )Build an SDK-source advisory entry for
errors[]augmentation.Sets
source=sdkandsdk_id=<package>@<version>per the multi-hop propagation contract incore/error.json. Consumers receiving this entry MUST treat it as advisory — the response stays success on the v1 path; only the v2 projection is degraded.Args
code- AdCP error code (e.g.,
FORMAT_DECLARATION_V1_AMBIGUOUS). Must be ≤64 chars per the wire schema. message- Human-readable description.
field- JSONPath-lite pointer to the offending field
(e.g.,
products[0].format_options[2]). details- Code-specific structured payload.
recovery- Recovery classification — defaults to
correctablebecause canonical-projection advisories tell the seller what to fix (addv1_format_ref, file a registry PR, etc.). suggestion- Optional one-line fix hint surfaced to operators.
Classes
class SdkAdvisory (**data: Any)-
Expand source code
class Error(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) code: Annotated[ str, Field( description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', max_length=64, min_length=1, ), ] message: Annotated[str, Field(description='Human-readable error message')] field: Annotated[ str | None, Field( description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." ), ] = None suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None retry_after: Annotated[ float | None, Field( description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', ge=1.0, le=3600.0, ), ] = None issues: Annotated[ list[Issue] | None, Field( description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' ), ] = None details: Annotated[ dict[str, Any] | None, Field( description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: <array>` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' ), ] = None recovery: Annotated[ Recovery | None, Field( description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' ), ] = None source: Annotated[ Source | None, Field( description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' ), ] = None sdk_id: Annotated[ str | None, Field( description='Optional identifier for the SDK that augmented this error entry. Format: `<sdk_package_name>@<version>` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var code : strvar details : dict[str, typing.Any] | Nonevar field : str | Nonevar issues : list[adcp.types.generated_poc.core.error.Issue] | Nonevar message : strvar model_configvar recovery : adcp.types.generated_poc.core.error.Recovery | Nonevar retry_after : float | Nonevar sdk_id : str | Nonevar source : adcp.types.generated_poc.core.error.Source | Nonevar suggestion : str | None
Inherited members