Skip to content

migrate value-shapes: the finding detail for a legacy {latitude, longitude} location is the missing-pair error, not the latitude rename hint the schema carries #15490

Description

@os-litant

Found while measuring #14060 (the re-scoped corpus-coverage question). Not a blocker for that card — the value is flagged and the gate does stay shut. What is wrong is the operator-facing prescription on the one legacy shape the scan's own header names.

The defect

valueShapeViolation (packages/objectql/src/validation/record-validator.ts:879) returns parsed.error.issues[0]?.message, and ValueShapeFinding.detail documents that string as "The first parse issue seen — the prescription an author acts on."

For a location value stored as {latitude, longitude} — the exact case scan-value-shapes.ts's header names as one the scan exists to find — zod emits three issues, and the actionable one is not first:

issues[0] code=invalid_type       path=["lat"] msg=Invalid input: expected number, received undefined
issues[1] code=invalid_type       path=["lng"] msg=Invalid input: expected number, received undefined
issues[2] code=unrecognized_keys  path=[]      msg=Unrecognized key(s) on this location value: `latitude`, `longitude`.
                                                   Did you mean `latitude` -> `lat`, `longitude` -> `lng`? ...

The retired spelling is a rename, not a typo — edit distance cannot reach latitude from lat, which is precisely why LocationValueSchema declares the aliases map. That hint is built, and then discarded.

Measured end-to-end through the real exported scan

Driving scanValueShapes (out of tree, origin/main @ a06faebbe4, @objectstack/spec and @objectstack/objectql freshly built):

A. legacy {latitude,longitude}
   scannedRecords=1 blocking=1 passed=false
   finding contact.geo (location) count=1
      detail -> Invalid input: expected number, received undefined
      names the rename hint? false
   | contact.geo (location) - 1 record(s): Invalid input: expected number, received undefined

B. #13388 seed {postal_code}          <- the address path, for contrast
      detail -> Unrecognized key(s) on this address value: `postal_code`.
                Did you mean `postal_code` -> `postalCode`? ...
      names the rename hint? true

C. CONTROL all-clean values           -> blocking=0 passed=true, "No malformed values found."

So an operator running os migrate value-shapes against real legacy location data is told expected number, received undefined and left to work out the rename themselves, while the identically-shaped address case gets the full prescription. The asymmetry is invisible from inside: address happens to declare every member optional, so no invalid_type issue can sort ahead of the unrecognized-key one; location requires the lat/lng pair, so two always do.

Why no gate catches it

  • packages/objectql/src/validation/scan-value-shapes.test.ts uses this exact value ({ geo: { latitude: 1, longitude: 2 } }) but asserts only report.blocking === 1 — never detail.
  • packages/spec/src/data/field-value.test.ts:313 does assert the rename text, and it reaches it by searching the issue list for the unrecognized_keys issue rather than reading issues[0]. The spec layer already knows the ordering; the consumer that formats the operator's report does not.

Suggested direction (not a decision)

Prefer the unrecognized_keys issue when one is present, rather than taking issues[0] positionally — an undeclared key is the more actionable diagnosis whenever both are reported, and it is the one carrying the alias map. Worth confirming that the same positional read does not mislead the other structured-JSON classes before changing it.

Provenance: #14060 (the measurement that surfaced this), #13802 (which closed both shapes), #13388 (the live drift instance), #3438 (the scan).


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions