Module adcp.canonical_formats.narrowing
FORMAT_DECLARATION_DIVERGENT narrowing check.
Per schemas/cache/<version>/core/product-format-declaration.json#v1_format_ref
(normative):
The v2 declaration's <code>params</code> MUST narrow (be compatible with) each
referenced v1 format's <code>requirements</code> — see the 'Narrows — formal
definition' section in canonical-formats.mdx. SDKs comparing
dual-emitted shapes (<code>Product.format\_ids\[]</code> ⊇ entries from
<code>v1\_format\_ref</code> AND <code>Product.format\_options\[]</code> carrying this
declaration) treat the link as the authoritative pairing and run
the narrowing check between this declaration and EACH referenced v1
format file's <code>requirements</code>.
"Narrows" is structural — v2 params MUST be a subset of the constraints v1 declared:
- Numeric maxima (
max_width,max_height,max_file_size_kb,max_duration_ms, …): v2's declared value MUST be ≤ v1's maximum. - Numeric minima (
min_width,min_height,min_dpi, …): v2's value MUST be ≥ v1's minimum. - Enum subsets (
image_formats,vast_versions, …): v2's declared set MUST be a subset of v1's allowed set. - Exact-equal scalars (
aspect_ratio,vast_versionwhen both declare a single value): MUST be equal.
The check is conservative — when v1 declares a constraint and v2 omits the matching field, that's NOT a divergence (v2 silently inherits the v1 cap, which is what "narrows" means). When v2 declares a constraint v1 doesn't mention, that's also not a divergence (v2 narrows into unconstrained space).
The check emits :class:Error advisories on
FORMAT_DECLARATION_DIVERGENT when divergence is detected, with
details enumerating each diverging field and the v1/v2 values so
the seller can reconcile.
Functions
def check_narrows(v2_params: dict[str, Any] | Any, v1_requirements: dict[str, Any] | Any) ‑> list[Divergence]-
Expand source code
def check_narrows( v2_params: dict[str, Any] | Any, v1_requirements: dict[str, Any] | Any, ) -> list[Divergence]: """Compare ``v2_params`` against ``v1_requirements`` and return divergences. Returns an empty list when ``v2_params`` narrows ``v1_requirements`` (the spec-conformant case). Returns a list of :class:`Divergence` records when divergent — one per diverging field. Call :meth:`Divergence.to_dict` to project a record onto the wire shape used in advisory ``details.divergences``. """ v2 = _as_dict(v2_params) v1 = _as_dict(v1_requirements) if not v2 or not v1: return [] divergences: list[Divergence] = [] for field_name in _MAX_FIELDS: v1_max = v1.get(field_name) if not _is_numeric(v1_max): continue # v2 may carry the cap directly OR the value being capped (e.g., # v1 declares ``max_width`` and v2 declares ``width``). v2_value = v2.get(field_name) if v2_value is None: v2_value = v2.get(field_name.removeprefix("max_")) if v2_value is None: continue # The value-being-capped form: v2 ``width`` against v1 ``max_width`` # is a "v2 value MUST be ≤ v1 cap" check. if _is_numeric(v2_value) and v2_value > v1_max: divergences.append( Divergence(field=field_name, kind="exceeds_max", cap=v1_max, value=v2_value) ) for field_name in _MIN_FIELDS: v1_min = v1.get(field_name) if not _is_numeric(v1_min): continue v2_value = v2.get(field_name) if v2_value is None: v2_value = v2.get(field_name.removeprefix("min_")) if v2_value is None: continue if _is_numeric(v2_value) and v2_value < v1_min: divergences.append( Divergence(field=field_name, kind="below_min", cap=v1_min, value=v2_value) ) for field_name in _ENUM_SUBSET_FIELDS: v1_set = v1.get(field_name) v2_set = v2.get(field_name) if v1_set is None or v2_set is None: continue if not _is_subset(v2_set, v1_set): divergences.append( Divergence( field=field_name, kind="not_subset", cap=_echo_set(v1_set), value=_echo_set(v2_set), ) ) for field_name in _EXACT_FIELDS: v1_value = v1.get(field_name) v2_value = v2.get(field_name) if v1_value is None or v2_value is None: continue if v1_value != v2_value: divergences.append( Divergence(field=field_name, kind="not_equal", cap=v1_value, value=v2_value) ) return divergencesCompare
v2_paramsagainstv1_requirementsand return divergences.Returns an empty list when
v2_paramsnarrowsv1_requirements(the spec-conformant case). Returns a list of :class:Divergencerecords when divergent — one per diverging field. Call :meth:Divergence.to_dict()to project a record onto the wire shape used in advisorydetails.divergences. def narrowing_advisory(declaration: ProductFormatDeclaration,
*,
v1_requirements: dict[str, Any],
v1_format_id: str,
field_path: str = 'format_options[]') ‑> adcp.types.generated_poc.core.error.Error | None-
Expand source code
def narrowing_advisory( declaration: ProductFormatDeclaration, *, v1_requirements: dict[str, Any], v1_format_id: str, field_path: str = "format_options[]", ) -> Error | None: """Build the ``FORMAT_DECLARATION_DIVERGENT`` advisory for a single pairing. Returns ``None`` when ``declaration.params`` narrows ``v1_requirements`` (no divergence to report). Returns an :class:`Error` with ``details.divergences`` listing the failing fields when divergent. Args: declaration: The v2 ``ProductFormatDeclaration`` carrying ``v1_format_ref[]``. v1_requirements: The referenced v1 format's ``requirements`` object (dict or Pydantic model). v1_format_id: The v1 format identifier (``id`` portion of the ``FormatId``) — surfaced in advisory details so adopters can locate the divergent pair when a declaration carries many refs. field_path: JSONPath-lite pointer for the advisory's ``field``. """ divs = check_narrows(declaration.params, v1_requirements) if not divs: return None safe_id = _echo_identifier(v1_format_id) return make_sdk_advisory( code="FORMAT_DECLARATION_DIVERGENT", message=( f"v2 declaration (format_kind={declaration.format_kind.value!r}) " f"params do not narrow v1 format {safe_id!r} requirements: " f"{len(divs)} divergence(s)." ), field=field_path, details={ "format_kind": declaration.format_kind.value, "v1_format_id": safe_id, "divergences": [d.to_dict() for d in divs], }, suggestion=( "Reconcile the v2 params against the referenced v1 format's " "requirements: lower the v2 cap, expand the v1 allowed set, " "or drop the v1_format_ref entry if the formats genuinely " "differ in shape." ), )Build the
FORMAT_DECLARATION_DIVERGENTadvisory for a single pairing.Returns
Nonewhendeclaration.paramsnarrowsv1_requirements(no divergence to report). Returns an :class:Errorwithdetails.divergenceslisting the failing fields when divergent.Args
declaration- The v2
ProductFormatDeclarationcarryingv1_format_ref[]. v1_requirements- The referenced v1 format's
requirementsobject (dict or Pydantic model). v1_format_id- The v1 format identifier (
idportion of theFormatId) — surfaced in advisory details so adopters can locate the divergent pair when a declaration carries many refs. field_path- JSONPath-lite pointer for the advisory's
field.
Classes
class Divergence (field: str, kind: DivergenceKind, cap: Any, value: Any)-
Expand source code
@dataclass class Divergence: """One narrowing-check failure between v2 ``params`` and v1 ``requirements``. Returned by :func:`check_narrows` and folded into the advisory's ``details.divergences`` list by :func:`narrowing_advisory`. The typed form lets callers route on ``kind`` without parsing dicts; the ``to_dict`` projection is what lands on the wire. Field semantics: * ``exceeds_max`` — v2 declared a value above v1's published cap. ``cap`` is the v1 maximum; ``value`` is the v2 over-cap declaration. * ``below_min`` — v2 declared a value below v1's published floor. ``cap`` is the v1 minimum; ``value`` is the v2 under-floor declaration. * ``not_subset`` — v2 declared an enum-typed set with values v1 doesn't allow. ``cap`` is the v1 allowed set; ``value`` is the v2 declared set. * ``not_equal`` — v2 disagreed on an exact-equal scalar. ``cap`` is the v1 value; ``value`` is the v2 value. The bound names (``cap``/``value``) trade some clarity for a single discriminated-union shape that's easier for adopters to switch over than four kind-specific records. """ field: str kind: DivergenceKind cap: Any value: Any def to_dict(self) -> dict[str, Any]: """Wire-shape projection used in advisory ``details.divergences``. Preserves the field-name vocabulary the half-2 implementation emits (``v1_max`` / ``v1_min`` / ``v1_allowed`` / ``v1_value`` on one side, ``v2_value`` / ``v2_declared`` on the other) so existing buyer-side parsers don't break on the typed switch. """ v1_key, v2_key = { "exceeds_max": ("v1_max", "v2_value"), "below_min": ("v1_min", "v2_value"), "not_subset": ("v1_allowed", "v2_declared"), "not_equal": ("v1_value", "v2_value"), }[self.kind] return { "field": self.field, "kind": self.kind, v1_key: self.cap, v2_key: self.value, }One narrowing-check failure between v2
paramsand v1requirements.Returned by :func:
check_narrows()and folded into the advisory'sdetails.divergenceslist by :func:narrowing_advisory(). The typed form lets callers route onkindwithout parsing dicts; theto_dictprojection is what lands on the wire.Field semantics:
exceeds_max— v2 declared a value above v1's published cap.capis the v1 maximum;valueis the v2 over-cap declaration.below_min— v2 declared a value below v1's published floor.capis the v1 minimum;valueis the v2 under-floor declaration.not_subset— v2 declared an enum-typed set with values v1 doesn't allow.capis the v1 allowed set;valueis the v2 declared set.not_equal— v2 disagreed on an exact-equal scalar.capis the v1 value;valueis the v2 value.
The bound names (
cap/value) trade some clarity for a single discriminated-union shape that's easier for adopters to switch over than four kind-specific records.Instance variables
var cap : Anyvar field : strvar kind : Literal['exceeds_max', 'below_min', 'not_subset', 'not_equal']var value : Any
Methods
def to_dict(self) ‑> dict[str, typing.Any]-
Expand source code
def to_dict(self) -> dict[str, Any]: """Wire-shape projection used in advisory ``details.divergences``. Preserves the field-name vocabulary the half-2 implementation emits (``v1_max`` / ``v1_min`` / ``v1_allowed`` / ``v1_value`` on one side, ``v2_value`` / ``v2_declared`` on the other) so existing buyer-side parsers don't break on the typed switch. """ v1_key, v2_key = { "exceeds_max": ("v1_max", "v2_value"), "below_min": ("v1_min", "v2_value"), "not_subset": ("v1_allowed", "v2_declared"), "not_equal": ("v1_value", "v2_value"), }[self.kind] return { "field": self.field, "kind": self.kind, v1_key: self.cap, v2_key: self.value, }Wire-shape projection used in advisory
details.divergences.Preserves the field-name vocabulary the half-2 implementation emits (
v1_max/v1_min/v1_allowed/v1_valueon one side,v2_value/v2_declaredon the other) so existing buyer-side parsers don't break on the typed switch.