Skip to content

Data API: reject unknown payload fields (validateRecord ignores keys not in schema) #1590

Description

@xuyushun441-sys

Summary

The generic data API silently accepts payload fields that are not declared on the object, passing them straight through to the driver. On SQLite this surfaces as a raw table X has no column named <field> error; the caller gets no field-level feedback. Mature low-code platforms (e.g. Salesforce INVALID_FIELD) reject unknown fields at the API layer — the schema is the contract.

Repro

POST /api/v1/data/sys_team with body { "name": "x", "label": "x" } (label is not a field on sys_team).
Before the error-mapping safety net (fix/rest-map-schema-errors) this returned 500 DATABASE_ERROR; with it, a 400 INVALID_FIELD. But that is a last-resort string-match on the driver error — the real fix is to reject the unknown field before building SQL.

Root cause

packages/objectql/src/validation/record-validator.ts validateRecord() iterates over declared schema fields, never over payload keys:

// insert mode
for (const [name, def] of Object.entries(fields)) { ... }  // schema-driven only

Keys present in data but absent from fields are never examined → they survive into the driver create()/update() call.

Proposed change

In validateRecord, after the declared-field walk, iterate Object.keys(data) and push a { code: unknown_field } error for any key that is not a schema field and not an allow-listed control key.\n\n### Open design points\n- Allow-list for legitimate non-column keys: $-prefixed control keys, nested related-record writes, computed/formula display fields the UI may echo back, system-injected columns. Need to enumerate before enforcing.\n- Rollout: hard-enforce now vs. log-then-enforce (one release warns, next rejects) to avoid 400-ing clients that currently send extras. Requires a quick audit of what @object-ui/console + the os CLI actually POST.\n- Applies to both insert and update (PATCH).\n\n## Why this matters long-term\nFixes the entire "unknown field" class at the correct layer, for all objects, and makes the API self-documenting — instead of relying on a dialect-specific driver-error regex in rest-server.ts mapDataError().\n\n## Related\n- Safety net already landed: branch fix/rest-map-schema-errors (PR pending) — mapDataError maps unknown-column/not-null → 4xx.\n- Pairs with the managedBy enforcement + provenance issues (linked below).\n\n_Found running cloud LOCAL-E2E-CHECKLIST B7 (per-env data API CRUD)._

Activity

  1. xuyushun441-sys commented on Jun 5, 2026

    @xuyushun441-sys
    CollaboratorAuthor

    Related set — three rungs of "the data API must honor object metadata before hitting the DB", surfaced together by LOCAL-E2E-CHECKLIST B7:

    Landed as the error-mapping safety net: branch fix/rest-map-schema-errors (maps the symptoms to 4xx; these issues are the causes).

  2. os-zhuang commented on Jun 15, 2026

    @os-zhuang
    Contributor

    Closing as deferred (startup-stage prioritization). Rationale:

    • No live data-integrity bug. An unknown field doesn't get silently written — the driver rejects the whole insert (no column named X). And the worst symptom (500 DATABASE_ERROR) is already mapped to a clean 400 INVALID_FIELD by the mapDataError safety net. So the user-facing pain is already handled.
    • The "proper layer" fix is API-hygiene polish with real back-compat cost — it would 400 any client (incl. our own console/CLI echoing computed/display fields) currently sending extras, and the author's own notes call for an allow-list audit + log-then-enforce rollout.
    • At current stage, premature input-strictness is net friction, not safety.

    Revisit at GA / enterprise hardening, or when a concrete integration demands strict field validation — at which point do it with the audit + staged rollout. The metadata-honoring causes for the related set are partly addressed (#1592 silent-NULL fixed).

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

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions