Skip to content

decision: keep precise droppedFields vs. introduce a generic write-path warnings envelope #3477

Description

@os-zhuang

Split out from #3455 / #3468 (the issue's open question).

Context

Single-write (#3448) and bulk (#3468) write responses now carry a precise, typed droppedFields: DroppedFieldsEvent[] — { object, fields, reason: 'readonly' | 'readonly_when' } — surfacing caller-supplied fields that were legally stripped before the write.

The current decision (shipped) is precise droppedFields, reusing DroppedFieldsEventSchema, chosen for symmetry between the single-write and bulk paths.

The open question

If more write-path warning classes are anticipated — e.g. value truncation, silent type coercion, deprecated-field writes, unit/precision normalization — a per-class field on every write response (droppedFields, truncatedFields, coercedFields, …) does not scale. A single generic envelope would:

warnings?: Array<{ code: string; object?: string; fields?: string[]; message?: string; meta?: … }>

with droppedFields becoming one code.

Trade-off

  • Keep precise (status quo): strongly typed, self-documenting, zero migration; but a new warning class = a new response field each time.
  • Generic envelope: one extensible channel; but a breaking-ish migration that must move single and bulk together (both shipped with droppedFields), and loses per-class static typing unless the union is well-modeled.

Recommendation

Defer unless/until a second concrete warning class is on the roadmap. If one appears, do the envelope migration once, across single + bulk simultaneously, and keep droppedFields as a typed alias/code for backward compatibility during a deprecation window.

Refs: #3455, #3468, #3448.

Activity

  1. os-zhuang commented on Jul 25, 2026

    @os-zhuang
    ContributorAuthor

    Closing as resolved by decision (2026-07-25): keep the precise, typed `droppedFields` — no generic warnings envelope.

    Verified there is no second write-path warning class anywhere today: no value truncation, no silent write-side coercion (`coerceBooleanFields` in the engine is result-side normalization, not a payload mutation), no existing `warnings` field anywhere in the API spec, and none of the anticipated classes (truncation / coercion / deprecated-field writes / unit normalization) is on any roadmap. The soft trigger this issue was waiting on has no candidate.

    Migration design, recorded so it never needs re-derivation — if a second class ever materializes:

    • Envelope as a discriminated union on `code` (`z.discriminatedUnion`), with `{ code: 'dropped_fields', object, fields, reason }` as the first member. Static per-class typing is fully preserved — the "loses typing unless well-modeled" concern in this issue dissolves.
    • Migrate single + bulk in one PR; dual-emit (`droppedFields` deprecated + `warnings`) for one minor window; removing `droppedFields` is major.
    • Client SDK reads `warnings` with `droppedFields` fallback; objectui's `ObjectStackAdapter.notifyDroppedFields` already reads structurally and needs one extra branch. The `X-ObjectStack-Dropped-Fields` header is orthogonal (single-write path only) and unchanged; no new CORS exposure. Effort ≈ 0.5–1× of feat(rest/protocol): extend droppedFields write-observability to bulk paths + client SDK (#3455) #3468.

    Hard triggers that should reopen this decision (instead of the soft "on the roadmap"):

    1. Any PR proposing a second per-class field on write responses — reviewers: point here and route it into the envelope instead.
    2. A real Virtual Data Engine response contract being authored (see tracking: droppedFields write-observability is asymmetric across the Virtual Data Engine RPC boundary (update best-effort, create surfaces) #3476) — decide the envelope question there, once, for the remote contract too.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions