Skip to content

FieldValueRequest can't tag scalar TIMESTAMP — Date/Time writes fail with BAD_REQUEST #375

@leogdion

Description

@leogdion

Summary

FieldValueRequest cannot tag a scalar value with its CloudKit type, so writing a Date/Time (TIMESTAMP) field is impossible. The .date FieldValue is serialized as a bare JSON number with no type, CloudKit infers INT64 from the number, and rejects it against the TIMESTAMP schema field with BAD_REQUEST.

This blocks CelestraCloud (and will affect BushelCloud): every record write that includes a timestamp field fails.

The issue CelestraCloud is having

CelestraCloud's schema declares timestamp fields as TIMESTAMP:

  • Feed: verifiedTimestamp, attemptedTimestamp
  • Article: fetchedTimestamp, expiresTimestamp, publishedTimestamp

The conversion code correctly emits FieldValue.date(...) for them. Every create/update then fails:

Batch 1 failed: CloudKit record operation failed for 'EB19FB9C-...' (BAD_REQUEST)
Reason: Invalid value, expected type TIMESTAMP.
...
Batch complete: 0/7 (0.0%)

Failing CI runs (CelestraCloud @ dbdba1a, MistKit v1.0.0-beta.2):

Note on the companion build failure (https://github.com/brightdigit/CelestraCloud/actions/runs/26305885729): CelestraError.swift referenced CloudKitError.zonePaginationLimitExceeded / .conversionFailed / .recordOperationFailed. Those cases exist in the current v1.0.0-beta.2 HEAD and a fresh build now passes — that run failed only because the v1.0.0-beta.2 branch pointer hadn't yet advanced to include the Zone API commits (#367/#368) when it ran. It resolves on re-run; no code change required. The timestamp write failure is the real, unfixed defect.

Root cause

A CloudKit Web Services field is { value: CKValue, type: string (optional) }. type is optional in general, but a Date/Time value is just a number on the wire — indistinguishable from INT64/DOUBLE. Without type: "TIMESTAMP", CloudKit infers INT64 and rejects it against a TIMESTAMP schema field. (Apple docs: Types and Dictionaries; matching error + fix in forum thread 76351, where the resolution is { "type": "TIMESTAMP", "value": 1493382919000 }.)

MistKit currently can't send that tag. The request type enum only contains the *_LIST filter types — no scalar TIMESTAMP:

# openapi.yaml:1027  (FieldValueRequest.type)
enum: [STRING_LIST, INT64_LIST, DOUBLE_LIST, BYTES_LIST, TIMESTAMP_LIST, REFERENCE_LIST, LOCATION_LIST, ASSET_LIST, LIST]

…and the serializer emits a bare DateValue (a Double) with no type:

// Sources/MistKit/OpenAPI/Components/Components.Schemas.FieldValueRequest.swift:111
if case .date(let value) = fieldValue {
  return Self(value: .DateValue(value.timeIntervalSince1970 * 1_000))  // no _type
}

The asymmetry: reads work because CloudKit supplies the type in responses (the response enum does include scalar TIMESTAMP, openapi.yaml:1051) and we use it to recover .date vs .double:

// Sources/MistKit/Models/FieldValues/FieldValue+Components.swift:132
if case .DoubleValue(let dblVal) = value {
  return fieldType == .TIMESTAMP
    ? .date(Date(timeIntervalSince1970: dblVal / 1_000))
    : .double(dblVal)
}

So the information exists in the domain model (.date is a distinct case) — the request path just drops it.

Proposed work

  1. Explain / reproduce the CelestraCloud failure above (timestamp writes → BAD_REQUEST). ✅ documented here.
  2. Add the missing scalar types to FieldValueRequest.type (openapi.yaml:1008), regenerate MistKitOpenAPI (Types.swift:952 _typePayload), and emit type when serializing the corresponding FieldValue. At minimum add TIMESTAMP and set it for .date. Investigate whether type can be made required on requests.
  3. If type can't be required (CloudKit documents it as optional, so a global requirement is likely wrong/over-restrictive), then enumerate and document exactly which types do require an explicit tag because their JSON value is otherwise ambiguous, and emit type only for those. Candidates:
    • TIMESTAMP — number, otherwise read as INT64/DOUBLEthe blocker
    • BYTES — base64 string, otherwise read as STRING
    • DOUBLE — whole-valued doubles can serialize without a fraction and be read as INT64
    • (object/array-shaped types — REFERENCE, ASSET, LOCATION, LIST — are unambiguous and should stay untagged)

Priority

Unblocking CelestraCloud is the goal — the timestamp write failure (and confirming the build failure clears on re-run). The minimal fix is to make .date writes carry type: "TIMESTAMP"; the broader ambiguous-type audit (item 3) can follow.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority: highShould fix soon

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions