Skip to content

feat(rest): surface silently-dropped write fields on PATCH/POST /data (#3431)#3448

Merged
os-zhuang merged 2 commits into
mainfrom
claude/patch-data-fields-dropped-4d0qpn
Jul 24, 2026
Merged

feat(rest): surface silently-dropped write fields on PATCH/POST /data (#3431)#3448
os-zhuang merged 2 commits into
mainfrom
claude/patch-data-fields-dropped-4d0qpn

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #3431. Follow-up to #3407 / #3413.

Problem

#3413 built the engine-level strip-observability channel
(WriteObservabilityOptions.onFieldsDropped) and wired the flow side
(update_record / create_record emit a step warning + droppedFields). The
REST write path was never wired, so an external API caller writing N fields
still got a bare 200 + record when readonly (#2948) / readonlyWhen (#3042)
stripping meant < N actually landed — the only way to notice was a per-field
diff of the returned row (which need not echo every field). Same silent-success
class #3407 fixed flow-side, just on HTTP.

Design decisions

The issue left the feedback shape "待拍板" (to be decided). This PR ships both
robust channels
, resolving the D1/D3 open points:

  • Passthrough (D3): the DataProtocol result carries an optional
    droppedFields event list — the issue's own sanctioned option ("protocol 结果
    附带事件列表"). This is the reliable, structured, cross-origin-safe surface and
    feeds every protocol consumer, not just REST.
  • REST header (D1): PATCH/POST additionally echo an
    X-ObjectStack-Dropped-Fields response header (zero body-contract intrusion,
    matching the existing X-Export-Styles: dropped precedent).

Status/success semantics are unchanged (200 update / 201 create) — a strip
is legitimate semantics, not a failure (same principle as #3413). The FLS write
gate is untouched (already fails closed with 403).

Changes

@objectstack/metadata-protocol

  • updateData registers an onFieldsDropped collector on engine.update and
    returns the events as droppedFields.
  • createData surfaces the 安全/设计:静态 readonly 的 INSERT 豁免让审批/状态字段可在创建时被直接播种(比 #3003 少一步) #3043 static-readonly ingress strip too — that
    strip runs at the protocol ingress (stripReadonlyForInsert), before the
    engine (which is INSERT-readonly-exempt), so it is recovered by diffing the
    supplied payload against the stripped one (diffDroppedFields). The engine's
    onFieldsDropped is also wired for a future insert-side engine strip. A faulty
    listener never breaks the write (the engine catches + logs).

@objectstack/spec

  • UpdateDataResponseSchema / CreateDataResponseSchema gain an optional
    droppedFields: DroppedFieldsEvent[] — present only on a drop, so the shape
    stays backward-compatible for clients that only read record.

@objectstack/rest

  • PATCH /data/:object/:id and POST /data/:object echo drops as the
    X-ObjectStack-Dropped-Fields header (field;reason=<reason> tokens,
    comma-joined) and keep the structured list on the body. Tolerates both the
    Hono-style res.header and node-style res.setHeader.

Testing

  • protocol.dropped-fields.test.ts — update forwards engine events (single +
    multi-pass); create surfaces the ingress strip; no-drop omits the key; system
    create keeps the field.
  • rest-dropped-fields.test.ts — PATCH/POST set the header + keep body
    droppedFields, multi-field/reason join, no-header when nothing dropped,
    create stays 201.
  • Full suites green: spec 6849, metadata-protocol 58, rest 335. tsc --noEmit
    clean for the touched files.

Out of scope (issue #3431 D2 open questions, deferred)

  • Bulk wiring: updateManyData / createManyData / batchData and GraphQL
    mutation.
  • Typed @objectstack/client warnings (the body droppedFields is already
    readable; typing it is a follow-up).
  • Adding the header to the Hono CORS exposeHeaders allow-list for cross-origin
    browser reads (three-site lockstep) — the body droppedFields is the
    cross-origin-safe channel meanwhile.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NNqeAmxCCq9jgLFwa9subq


Generated by Claude Code

…#3431)

Wire the engine's `onFieldsDropped` strip-observability channel (#3413) through
the DataProtocol and REST write path so an external API caller is no longer
silently stripped when `readonly` (#2948) / `readonlyWhen` (#3042) drops
caller-supplied fields. The write still succeeds — this only makes the strip
observable (the same silent-success class #3407 fixed flow-side).

- metadata-protocol: `updateData` collects the engine's `onFieldsDropped`
  events; `createData` surfaces the #3043 static-`readonly` INGRESS strip via a
  payload diff (that strip runs BEFORE the engine, which is INSERT-readonly-
  exempt, so the engine listener never sees it). Both attach an optional
  `droppedFields` list to the response when ≥1 field was dropped.
- spec: `UpdateDataResponseSchema` / `CreateDataResponseSchema` gain an optional
  `droppedFields: DroppedFieldsEvent[]` — present only on a drop, so the shape
  stays backward-compatible for clients that only read `record`.
- rest: PATCH `/data/:object/:id` and POST `/data/:object` echo drops as the
  `X-ObjectStack-Dropped-Fields` response header and keep the structured list on
  the body. Status/success semantics unchanged (200 update / 201 create).

Tests: protocol passthrough (update forwards engine events; create surfaces the
ingress strip; no-drop omits the key) and REST header/body (single + multi
field, no-drop, create 201).

Deferred (issue #3431 D2 open questions): bulk (`updateManyData` /
`createManyData` / `batchData`) and GraphQL mutation wiring, typed
`@objectstack/client` warnings, and adding the header to the Hono CORS
`exposeHeaders` allow-list for cross-origin browser reads.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNqeAmxCCq9jgLFwa9subq
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 24, 2026 3:50pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m labels Jul 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/metadata-protocol, @objectstack/rest, @objectstack/spec.

104 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol, packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/rest, @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

…#3431)

`content/docs/references/api/protocol.mdx` is generated from the spec Zod
schemas (build-docs.ts) and is checked in; the new optional `droppedFields`
on Create/Update DataResponse must be regenerated so `check:docs` (run inside
the TypeScript Type Check job) stays green. Generated output only — no
hand-edits.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNqeAmxCCq9jgLFwa9subq
@os-zhuang
os-zhuang marked this pull request as ready for review July 24, 2026 15:10
@os-zhuang
os-zhuang merged commit 5ac93d4 into main Jul 24, 2026
16 of 17 checks passed
@os-zhuang
os-zhuang deleted the claude/patch-data-fields-dropped-4d0qpn branch July 24, 2026 15:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rest: PATCH /data 对被静默剥离的写入字段无任何回传 — onFieldsDropped 通道未接线(follow-up #3407/#3413)

2 participants