Summary
SPEC.md's Optional Fields rule states:
Fields not listed in the required array of the OpenAPI schema must be
nullable or optional in the language's type system. Sentinel values (empty
string, 0, etc.) are not acceptable substitutes for absence.
The rich-text *_attachments follow-up (#433) fixed this for optional arrays
in Kotlin (List<T>? = null instead of = emptyList()), and a generated-output
guard now pins it. But optional non-array scalars still collapse to
zero-value sentinels, violating the rule:
- Kotlin emits optional non-nullable scalars with zero-value defaults:
val completed: Boolean = false, val commentsCount: Int = 0,
val byteSize: Long = 0L, val position: Int = 0. An absent optional boolean
decodes to false, indistinguishable from a present false; an absent int → 0.
- Go optional non-pointer scalars collapse absence on decode too: an
absent field and an explicit zero both unmarshal to the zero value, so they are
indistinguishable. Only Go pointer fields (*T) preserve the distinction.
(omitempty additionally drops zero-values on re-encode.) SPEC.md §10.
- Ruby / Swift decode faithfully but omit
nil on re-encode (pre-existing encoder
behavior, documented in SPEC.md §10).
Scope
Pre-existing, SDK-wide debt broader than the #433 array fix (which was
deliberately array-scoped). The Kotlin scalar case is the clearest static-type
violation: fixing it means optional scalars become Boolean? = null / Int? = null
in the generator — another API-breaking change, and a cascade across hand-written
code + tests similar to the array fix.
Why filed now
rubric-audit.json criterion 1B.4 ("Optional Fields") is set to pass: false
referencing this issue, because the criterion is not fully met while optional
scalars still use sentinels. This issue tracks closing that gap (or an explicit
decision to accept the scalar zero-value trade-off and document it as the encode-only
edge already is).
Acceptance
- Optional scalar fields preserve absence in Kotlin's static types (and any other
SDK found to collapse them on decode), or
- A documented, agreed decision recording the residual encode-only collapses as
acceptable, with 1B.4 updated to pass: true + that rationale.
Summary
SPEC.md's Optional Fields rule states:
The rich-text
*_attachmentsfollow-up (#433) fixed this for optional arraysin Kotlin (
List<T>? = nullinstead of= emptyList()), and a generated-outputguard now pins it. But optional non-array scalars still collapse to
zero-value sentinels, violating the rule:
val completed: Boolean = false,val commentsCount: Int = 0,val byteSize: Long = 0L,val position: Int = 0. An absent optional booleandecodes to
false, indistinguishable from a presentfalse; an absent int →0.absent field and an explicit zero both unmarshal to the zero value, so they are
indistinguishable. Only Go pointer fields (
*T) preserve the distinction.(
omitemptyadditionally drops zero-values on re-encode.) SPEC.md §10.nilon re-encode (pre-existing encoderbehavior, documented in SPEC.md §10).
Scope
Pre-existing, SDK-wide debt broader than the #433 array fix (which was
deliberately array-scoped). The Kotlin scalar case is the clearest static-type
violation: fixing it means optional scalars become
Boolean? = null/Int? = nullin the generator — another API-breaking change, and a cascade across hand-written
code + tests similar to the array fix.
Why filed now
rubric-audit.jsoncriterion 1B.4 ("Optional Fields") is set topass: falsereferencing this issue, because the criterion is not fully met while optional
scalars still use sentinels. This issue tracks closing that gap (or an explicit
decision to accept the scalar zero-value trade-off and document it as the encode-only
edge already is).
Acceptance
SDK found to collapse them on decode), or
acceptable, with 1B.4 updated to
pass: true+ that rationale.