Skip to content

feat: gate semantic coercions behind explicit per-field consent - #7

Closed
Baoyuan-Xing wants to merge 1 commit into
fsaintjacques:mainfrom
Baoyuan-Xing:semantic-consent
Closed

Baoyuan-Xing wants to merge 1 commit into
fsaintjacques:mainfrom
Baoyuan-Xing:semantic-consent

Conversation

@Baoyuan-Xing

Copy link
Copy Markdown

Why

The risk classes describe two different kinds of danger, and the global --coerce switch currently treats them the same.

Truncation / precision loss / runtime check / lossless are value-level effects a caller can accept globally — "I'm fine with narrower numbers" is a coherent blanket policy.

Semantic coercions change what the bytes mean: Timestamp → int64 and Date32 → int32 impose an epoch/unit interpretation the source type doesn't carry (millis? micros? days?); signed ↔ unsigned reinterprets bit patterns; utf8 ↔ bytes crosses a meaning boundary. Whether each is right is a per-field design decision — a global flag can't express it, and enabling them silently invites encodes that succeed while meaning something else entirely. (Motivating case: a pipeline running --coerce for legitimate int-narrowing would silently encode a TIMESTAMP column into an int64 proto field with a unit nobody chose.)

What

--coerce / coerce_all now enables every risk class except Semantic. Semantic coercions require explicit per-field consent — [(apb.apb).coerce = true] on the field, or an explicit binding — where the decision is visible in the proto and reviewed with it.

  • resolve_type_check takes CoercionConsent { None | Global | Explicit } instead of a bool
  • inference: annotation → Explicit, coerce_all → Global; explicit bindings with coerce: true → Explicit
  • new TypeErrorReason::SemanticCoercionNotExplicit with an actionable message: "semantic coercion requires an explicit per-field (apb.apb).coerce annotation; not enabled by global coercion"
  • validation reports distinguish the same case; README updated

Compatibility

  • Explicit bindings and annotations: unchanged (still enable everything, including Semantic)
  • The only behavior change: coerce_all × Semantic-risk pairs now reject at plan time with the actionable error, where they previously coerced silently — which is the point
  • resolve_type_check's signature changes (bool → enum); internal callers updated

Tests

194/194 (4 new): consent matrix at the type-check level (Global enables non-semantic, rejects all four semantic families; Explicit enables all), plus an inference-level test that coerce_all fails a utf8→bytes name-match with an error directing to the annotation. Clippy clean.

The coercion risk classes describe two different kinds of danger. Truncation,
precision loss, runtime checks, and lossless widenings are value-level
effects a caller can reason about globally: "I accept narrower numbers" is a
coherent blanket policy, so the global switch (`coerce_all` / `--coerce`)
enabling them wholesale is sound.

Semantic coercions are not like that. Timestamp → int64 and Date32 → int32
impose an epoch/unit interpretation the source type does not carry;
signed ↔ unsigned reinterprets bit patterns; utf8 ↔ bytes crosses a meaning
boundary. Whether each is correct depends on the specific field — which unit
downstream readers expect, whether ids are genuinely non-negative. A global
flag cannot express that judgment, and enabling these silently invites the
worst kind of bug: encodes that succeed while meaning something else.

This change makes the global switch enable every risk class EXCEPT
Semantic. Semantic coercions now require explicit per-field consent — a
`[(apb.apb).coerce = true]` annotation or an explicit binding — where the
decision is visible in the proto and reviewed with it.

Mechanically: `resolve_type_check` takes a `CoercionConsent`
(None / Global / Explicit) instead of a bool; inference maps the per-field
annotation to Explicit and `coerce_all` to Global; explicit bindings map
`coerce: true` to Explicit. A new `TypeErrorReason::SemanticCoercionNotExplicit`
tells callers exactly what to do:

  semantic coercion requires an explicit per-field (apb.apb).coerce
  annotation; not enabled by global coercion

Validation reports distinguish the same case. Behavior of explicit bindings
and annotations is unchanged; the only new rejection is Global-consent ×
Semantic-risk, which previously succeeded silently.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant