Skip to content

Commit 4393318

Browse files
claude[bot]claude
andauthored
docs(metadata-protocol): the create-strip aggregation states what the tree does, not a premise it falsifies (#17293)
The docblock on `mergeDroppedFieldEvents` justified `createManyData`'s aggregated `droppedFields` with a second, falsified reason: that the create-side static-`readonly` strip is "schema-uniform — every row drops the same set". Maintainer ruling C (#14147) falsifies it. The strip runs inside `engine.insert`, after `beforeInsert`, and exempts keys a hook itself assigned — indexed per row at the call site (`hookWrittenKeys: rowHookWrittenKeys[i]`, packages/objectql/src/engine.ts), so a hook that stamps a protected key on some rows and not others makes those rows drop different sets. The first reason the docblock already gave is the correct one and is the one `packages/spec` kept when the sibling carrier was corrected: the `{ object, records, count }` response has no per-row slot, so a union is the only view it can represent. The shape is unchanged; only the justification and the reader guidance move. Reader guidance matches the landed spec wording — read a name as "at least one row dropped this field", not "every row did". Three carriers in this package, judged by claim rather than by spelling: the docblock, the bulk test's header comment, and a test TITLE that named the pinned behaviour by the falsified premise. The test's assertions are untouched — the aggregate is still right for the first reason. Claude-Session: https://claude.ai/code/session_01XTBcV7zZHmokdyQgXjbyEU Co-authored-by: Claude <noreply@anthropic.com>
1 parent 386de16 commit 4393318

2 files changed

Lines changed: 25 additions & 9 deletions

File tree

‎packages/metadata-protocol/src/protocol.dropped-fields.bulk.test.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
// - updateManyData / batchData → per-row `droppedFields` on each result row;
1111
// - insertManyData → per-row `droppedFields` on each outcome;
1212
// - createManyData → aggregated top-level `droppedFields` (its
13-
// response has no per-row slot; the insert strip is schema-uniform).
13+
// response has no per-row slot, so a union is the only view it can
14+
// represent; read a name there as "at least one row dropped this field",
15+
// never "every row dropped the same set" — ruling C (#14147) exempts keys a
16+
// `beforeInsert` hook assigned, recorded per row, so rows CAN differ).
1417

1518
import { describe, it, expect, vi } from 'vitest';
1619
import { assertEngineUpdateDispatch, assertEngineFindOnePredicate } from '@objectstack/metadata-core';
@@ -87,7 +90,7 @@ describe('createManyData — aggregated top-level droppedFields (#3455)', () =>
8790
return { p: new ObjectStackProtocolImplementation(engine as any), engine };
8891
}
8992

90-
it('aggregates the schema-uniform create strip across rows into one event', async () => {
93+
it('two rows forging the same readonly key surface ONE aggregated top-level event', async () => {
9194
const { p } = makeProtocol();
9295
const res: any = await p.createManyData({
9396
object: 'approval_case',

‎packages/metadata-protocol/src/protocol.ts‎

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1873,14 +1873,27 @@ const CLONE_STRIP_FIELDS: readonly string[] = [
18731873
* `(object, reason)` with the UNION of dropped field names.
18741874
*
18751875
* Used by the bulk-create surface (`createManyData`), whose `{ object, records,
1876-
* count }` response has no per-row slot to hang a `droppedFields` on. The
1877-
* create-side static-`readonly` strip is schema-uniform — every row drops the
1878-
* same set — which makes an aggregated view faithful rather than lossy. (Since
1879-
* #14147 that strip is the ENGINE's, which reports one event per CALL for it,
1880-
* so the aggregation is over the runtime-owned per-row events.) Returns `[]` when nothing was dropped so callers can spread
1876+
* count }` response has no per-row slot to hang a `droppedFields` on — a union
1877+
* is the only view that response can represent, which is the whole reason this
1878+
* collapse exists. (Since #14147 that strip is the ENGINE's, which reports one
1879+
* event per CALL for it, so the aggregation is over the runtime-owned per-row
1880+
* events.)
1881+
*
1882+
* ⚠️ So read a name in a merged event as "AT LEAST ONE row dropped this field",
1883+
* never "every row dropped the same set". Maintainer ruling C (#14147) put the
1884+
* static-`readonly` strip INSIDE `engine.insert`, AFTER the `beforeInsert`
1885+
* hooks, where it exempts keys a hook itself assigned — recorded PER ROW and
1886+
* indexed per row at the call: `packages/objectql/src/engine.ts` hands
1887+
* `stripReadonlyFields` the option `hookWrittenKeys: rowHookWrittenKeys[i]`,
1888+
* and that option's only power is to turn a STRIP into a KEEP. A hook that
1889+
* stamps a protected key on some rows and not others therefore makes those rows
1890+
* drop DIFFERENT sets, so the union is faithful to the BATCH without being
1891+
* faithful to any one row.
1892+
*
1893+
* Returns `[]` when nothing was dropped so callers can spread
18811894
* `...(x.length ? { droppedFields: x } : {})` and keep the omit-when-empty shape.
1882-
* The per-row `insertMany`/`batch` paths keep row precision instead (they have a
1883-
* per-row result to carry it).
1895+
* The per-row `insertMany`/`batch` paths carry their own per-row `droppedFields`
1896+
* instead — they have a per-row result to hang one on.
18841897
*/
18851898
function mergeDroppedFieldEvents(events: DroppedFieldsEvent[]): DroppedFieldsEvent[] {
18861899
if (events.length === 0) return [];

0 commit comments

Comments
 (0)