Skip to content

finding: cloneData is the one create face whose response contract carries no droppedFields — a clone that copies or overrides a readonly column is stripped and warned, but not reported on the wire #15703

Description

@claude

Found while patching PR #15395 (the create-side static-readonly strip moving into engine.insert, card #14147); filed unassigned and out of that PR's scope because closing it changes a published response contract.

What is true today

After the 2026-09-03 ruling (option C on #14147), every DataProtocol create face hands the caller's payload to engine.insert whole and the engine strips a non-system caller's static-readonly keys. Five of the six create-shaped faces surface the engine's onFieldsDropped as their response droppedFieldscreateData, createManyData, insertManyData, and batchData's create rows and both arms of upsert that create. cloneData does not. It passes no listener, and its response contract, CloneDataResponseSchema (packages/spec/src/api/protocol.zod.ts, #11924, declared AS PRODUCED by maintainer ruling 2026-08-25), is exactly { object, id, sourceId, record }; packages/metadata-protocol/src/search-clone-schema-conformance.test.ts asserts in so many words that the body carries no droppedFields. PR #15395 pins that absence with its reason (protocol.readonly-insert.test.ts, the firing-control block) and narrows its changeset sentence to the faces that report.

Why it is worth a card

A clone IS a create, and it is the one create shape that can carry a read-only column without the caller typing it: cloneData copies the source row (approval_status: 'approved' included) and applies overrides on top, then inserts. The engine strips the copied or overridden value and logs the warn line — but the 201 body says nothing, so a caller that cloned an approved record and reads record.approval_status: 'draft' back has no field in the response telling it why. Every other create face now answers that question on the wire; the clone answers it only in server logs.

Suggested shape (a decision, not a patch)

Either add an optional droppedFields member to CloneDataResponseSchema (same shape as CreateDataResponseSchema's), wire the listener in cloneData exactly as createData does, and flip the two conformance pins and the firing-control pin — a @objectstack/spec surface widening, minor under the bump-level rule — or record that a clone deliberately does not report drops and say why on the schema. Both are legitimate; neither is a delegation detail of #14147.

Related: PR #15395, card #14147, the contract review at comment 5548671173 on #14147 (row A12).


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