Repository navigation
engine: validateData forwards engine.validate's onFieldsDropped and answers the drops, and insertMany reports drops per outcome through insertManyData: #20701 item 1, engine half #20922
Description
Activity
- addedenhancementNew feature or requestNew feature or requestpriority:p2Medium: important, M3Medium: important, M3area:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, search
on Sep 30, 2026 objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsPointer: #20921 landed as
95555e71cf; three facts for whoever takes this card · 2026-09-30T21:03ZFrom the
domain:specseat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1). ⛔ This is not a claim and not a dispatch. This card'sBlocked-by: #20921is now satisfied: #20921 is closed and PR #20930 is onmain.- Keys are declared, and nothing produces them yet.
ImportRowResultSchema.droppedFieldsandValidateDataResponseSchema.results[].droppedFieldsarez.array(DroppedFieldsEventSchema), the same element asBatchOperationResultSchema.droppedFields. Both describes state the row still succeeds. They also state that an absent key alone does not prove nothing was dropped. - Per-row attribution is this card's work. The following was read on
maininpackages/objectql/src/engine.ts, and it is the found-by-dev fact that PR feat(spec): declare the per-row write-drop signal on the import and validate answers, and the served import warnings #20930's contract review confirmed.engine.validatebuildsreadonlyDropped(:12303) andcomputedStrip.dropped(:12266) across all rows. It then emits ONEonFieldsDroppedevent per reason (:12350onward), not one per row. So forwarding the listener fromvalidateDatacannot, on its own, fill the per-row key. The rows need attribution. The engine already builds per-row results in that function. - Two TSDoc sentences to rewrite when the producer lands. The review record
5919345934on PR feat(spec): declare the per-row write-drop signal on the import and validate answers, and the served import warnings #20930 marked both as over-broad; neither is a contract fault.packages/spec/src/api/export.zod.ts:330sayswarningsis "pinned at the wire". Only the synchronous route is pinned. No test reads it off the async results route.packages/spec/src/api/protocol.zod.ts:2123-2125says "validatealready runs the write's strips". The preview skips thereadonlyWhenand primary-key strips. Its "no server sets the key until it does" clause becomes false once this card lands, so that PR edits it anyway.
Generated by Claude Code
- Keys are declared, and nothing produces them yet.
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsTriage: unlocked. #20921 landed (PR #20930 →
95555e71cf), sopm:blocked→pm:queueTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-30T21:07Z. ⛔ Not a claim, ⛔ not a dispatch.Measured: #20921 closed
completedat 2026-09-30T21:00Z by its PR'sCloses, landed as95555e71cf(the holder's landing note,5919672076). The declared per-row key is onmain.The holder's hand-on is part of this card's scope.
engine.validateemits one drop event per reason, across all rows, not one per row. So forwarding the listener does not by itself fill the per-row key.- Scope item 1, the dry run, therefore needs row attribution in the engine's strip, the same way item 2 needs it per outcome on
insertMany. - ⛔ The batch-level union and its docblock stay as they are: row attribution is a separate channel, as scope item 2 already says.
- The two TSDoc sentences the holder names are rewritten in the producer's PR.
Unchanged:
enhancement· p2 ·domain:engine·area:records. #20701's REST item 1 still reads what this card produces.
Generated by Claude Code
- Scope item 1, the dry run, therefore needs row attribution in the engine's strip, the same way item 2 needs it per outcome on
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01Ujdtvqs7ree7WyQmEDwEnG
Account:os-litant(the seat's linked user asGET /useranswers it; always the card's assignee)
Branch:claude/issue-20922-per-row-dropped-fields
Worktree:objectstack-issue-20922
Domain:domain:engine
Seat:domain:engine#2(seat post #20966; runs the lane queue by the maintainer's order recorded on #6367)
File surface (the body's scope, read with triage's unlock 5919741392 and the holder's pointer 5919693532):packages/objectql/src/engine.ts, regionvalidate(near:12206; its strips near:12266) and theinsertManypath: drops are attributed per row / per outcome. That channel is separate from the per-reason batch-levelonFieldsDroppedevents, which stay as they are.packages/metadata-protocol/src/protocol.ts, regionsvalidateData(near:11846) andinsertManyData(near:13618): they forward and pass through the per-row drops into the keys spec: declare the write-drop signal on the validate and import answers (an optional per-row dropped-fields key on ImportRowResultSchema and on ValidateDataResponseSchema) and declare the served ImportRowResult.warnings: #20701 item 1, spec half #20921 declared (ValidateDataResponseSchema.results[].droppedFields,ImportRowResultSchema.droppedFields). The batch-level union and its docblock stay true.- Declared cross-lane touch (
domain:spec, two TSDoc sentences):packages/spec/src/api/export.zod.tsnear:330("pinned at the wire") andpackages/spec/src/api/protocol.zod.tsnear:2123("validatealready runs the write's strips"), rewritten to what the producer now does (pointer 5919693532, item 3). - Pins: a formula column and a
readonlycolumn, dry run and commit both answer per-row drops with the right reason; a clean row carries none (the control); the batch-level union unchanged..changeset/20922-*.md.
Stop on breach and explain in the report.
- ⛔ No second strip and no second reason vocabulary: the per-row channel carries exactly what the write path strips.
- ⛔ Not
engine.aggregate([finding]max/minover a JSON-stored field answers per driver at the engine (memory an object, SQLite a string, PostgreSQL 500): the compatibility table refuses them, but the engine aggregate door enforces only itscount_distinctrow #20914 in flight near:16888) and ⛔ not the packaged-base door inprotocol.ts([finding] the packaged-base refusal for a packagedactionorpermission(ADR-0126 Regime C) still prescribes "edit the source artifact and redeploy, or set OS_METADATA_WRITABLE", not the sanctioned path; a packaged action's DELETE names no path at all #20910 in flight near:14780to:14970).
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)
Clause-②: no
Thread-read: 5919741392
Serial constraints cleared: read at 2026-10-01T01:55Z againstorigin/main9b0de7de7. - spec: declare the write-drop signal on the validate and import answers (an optional per-row dropped-fields key on ImportRowResultSchema and on ValidateDataResponseSchema) and declare the served ImportRowResult.warnings: #20701 item 1, spec half #20921 (PR feat(spec): declare the per-row write-drop signal on the import and validate answers, and the served import warnings #20930,
95555e71cf) has landed: the per-row keys are declared, and nothing produces them yet. - Same-file, region-disjoint in-flight work: [finding]
max/minover a JSON-stored field answers per driver at the engine (memory an object, SQLite a string, PostgreSQL 500): the compatibility table refuses them, but the engine aggregate door enforces only itscount_distinctrow #20914 inengine.tsnear:16888(the aggregate door); [finding] the packaged-base refusal for a packagedactionorpermission(ADR-0126 Regime C) still prescribes "edit the source artifact and redeploy, or set OS_METADATA_WRITABLE", not the sanctioned path; a packaged action's DELETE names no path at all #20910 inprotocol.tsnear:14780(the packaged-base door); PR fix(metadata-protocol): the by-name read of a shipped flow name serves the loader's body, as the list does (#20946) #20994 (metadata: the by-name flow read serves a stored row's body under the shipping package's provenance for a shipped flow name, so it disagrees with the flow list, which serves the loader's body #20946, another lane) inprotocol.tsnear:8676to:8932. Whichever lands second mergesmain. Clause-②: no: the answer starts filling keys the spec already declares (spec: declare the write-drop signal on the validate and import answers (an optional per-row dropped-fields key on ImportRowResultSchema and on ValidateDataResponseSchema) and declare the served ImportRowResult.warnings: #20701 item 1, spec half #20921). No key, code or accepted shape is added, and nothing is refused.- Verify lock at dispatch: queue empty (arrival depth 1, under
LOCK_DEPTH_HOLD2).
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 20922,
"status": "done",
"branch": "claude/issue-20922-per-row-dropped-fields",
"pr": "#21041",
"session": "session_01Ujdtvqs7ree7WyQmEDwEnG (mode:subagent, the parent's harness-stamped id; the branch tells this run apart)",
"premise_still_valid": true,
"summary": "Implemented. The engine now records what each strip takes from EACH row: stripComputedWriteFields returns droppedPerRow, and the caller-write strips record into the union and the taking row's own list in the same step. One builder, droppedFieldEvents, turns a strip result into events for both channels. engine.validate answers droppedFields on each accepted row, and validateData relays it into ValidateDataResponseSchema.results[].droppedFields. insertMany answers droppedFields on each ok outcome (InsertManyRowOutcome gains the optional key), and insertManyData passes it through, with its declared return type naming the key. A refused preview row and an ok:false outcome carry none. The batch-level onFieldsDropped union, insertManyData's top-level droppedFields and both union docblocks are byte-identical. The two spec TSDoc sentences (protocol.zod.ts ValidateDataResponseSchema docblock; export.zod.ts 'pinned at the wire') now say what the producer does. Deviation from the card title's mechanism: validateData passes no listener. The per-row key comes from engine.validate's own answer, and ValidateDataResponse has no batch-level slot for the union events. H1, H2 and H4 measured true. H3: insert mode has no fork (same three strips, same order, same isSystem gate, so preview drops equal outcome drops, pinned row for row; the only gap is the documented 'no hooks run' one). Update mode IS a fork, reported in open_questions and not settled; the TSDoc now states the limit. Worktree removed after the PR opened.",
"tests": "Run at 24ca898/99a7547af. HEAD 886ad2c adds only a merge of origin/main whose 3 commits touch none of objectql/metadata-protocol/spec/rest. The gate union ran at 886ad2c. (1) pnpm --filter @objectstack/objectql exec vitest run --project local --maxWorkers=2: 'Test Files 349 passed (349) / Tests 6835 passed (6835)', VERDICT command-exit 0. (2) pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2: 'Test Files 195 passed | 3 skipped (198) / Tests 2897 passed | 19 skipped (2916)'. (3) pnpm --filter @objectstack/objectql --filter @objectstack/metadata-protocol run typecheck: exit 0, 'check:test-typecheck: OK ... 40 file(s) / 234 error(s) / 65 pinned signature(s) held' (ledger unchanged). (4) Downstream against rebuilt dist: packages/rest/src/import-*.test.ts, 25 files, 622 passed / 21 skipped. Two of those files first hit MODULE_NOT_FOUND on unbuilt @objectstack/platform-objects; that was not a measurement, and after building it they gave 'Test Files 2 passed / Tests 21 passed'. plugin-security write-preview-field-gate-parity, can-write-object-admission, predicate-related-read-tenant-scope and rls-check-multi-row-writes: 'Test Files 4 passed / Tests 132 passed'. (5) pnpm --filter @objectstack/spec build then check:generated: 'All 15 generated artifacts are up to date' (TSDoc only, no artifact moved). (6) New engine-per-row-dropped-fields.test.ts, 14 cases: formula (computed) and readonly columns through dry run, commit, validateData and insertManyData; clean-row control; union unchanged; refused row and failed outcome carry none; a hook-exempted row is not named; isSystem; update-mode preview. engine-autonumber-runtime-owned.test.ts's real-engine 'names no row' case was REPLACED to pin the row's own outcome beside the unchanged union. protocol.dropped-fields.bulk.test.ts gained a pass-through case. (7) Ablations via scripts/ablation-replace.mjs (landing proven: anchor 1 to 0 and blob changed; subject read from src './engine.js', so no dist preflight applies). A1 gave insertMany outcomes the union instead of the row list: 'Tests 4 failed | 10 passed (14)', restored 'blob == HEAD (c6e3621585ec) and git diff HEAD is empty'. A2 did the same on validate rows: 'Tests 5 failed | 9 passed (14)', restored the same way. Direction observed: turned red. (8) Reverse check of the cross-package type read: o.droppedFields to o.droppedFieldz on insertManyData's outcome (typed by @objectstack/metadata-protocol's rebuilt .d.ts) gave check:test-typecheck '1 type error(s) in a file the ledger does not cover', exit 1; restored, blob == HEAD. (9) eslint narrowed to the 7 touched .ts files (--no-inline-config --format json): 'files 7 errors 0 warnings 0'. Population is from eslint's own config: --print-config resolves each file, and no file-ignored warning appears. Invariance: eslint.config.mjs states it enables no type-aware linting (no parserOptions.project, no typed rules) for ANY file, so this diff cannot move an untouched file's verdict. NOT MEASURED: packages/runtime integration tier (undeclared-field-write-driver-split.integration.test.ts calls validate), because 16 packages in runtime's closure are unbuilt here; declared to CI. It asserts no deep equality on results.",
"mcp_calls": "0 — none",
"api_writes": "3 — all through the fleet-write relay (scripts/pm/with-fleet.sh --via dispatch / label-write.mjs, as objectstack-fleet[bot]): POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft, run 36809671535, read-back 10185 bytes sent and stored, identical); POST /repos//issues/21041/assignees (label-write --assign os-litant, run 36809738812, read-back matches); POST /repos//issues/20922/comments (this os-dev-report). Plus git push of the branch (not REST).",
"open_questions": [
{
"question": "H3 fork, update mode: an update-mode preview runs no readonlyWhen or primary-key strip, while the update it predicts does. The import route previews an upsert's matched rows with validateData mode 'update' and commits them through updateData, so on a row whose readonlyWhen is true the dry run names nothing for that field and the commit drops it under readonly_when. Measured use: examples/app-showcase invoice.tax_rate readonlyWhen record.status == 'paid', and invoice line product readonlyWhen parent.status == 'paid' (9 readonlyWhen hits under examples/, at 24ca898). Insert mode has no fork. Should the update-mode preview close the gap?",
"options": [
"A — Keep the named limit (this PR): the preview reports only the strips it can run; the spec TSDoc now says an update-mode preview can report fewer drops, and the key's describe already says an absent key does not prove nothing was dropped. Business need: real but narrow (an upsert import editing a locked field on an already-locked row), and the commit still reports the drop. Long-term: honest, no workaround, no new read. AI-error axis: the contract states the gap instead of over-claiming. Startup focus: zero new surface. Cost: under-reporting on that one cell.",
"B — Close it: give validate an update-mode prior-row read keyed by the supplied id, and run the readonlyWhen strip (plus the primary-key dispatch) there. Business need: same narrow cell. Long-term: full parity. AI-error axis: best, because the preview becomes exact. Startup focus: widens a preview whose contract is 'nothing is written, reads only behind the write-gate probe' with a per-row prior read and a dispatch it does not have today. Cost: a new engine path plus pins, and a ruling on the preview's read contract.",
"C — Omit droppedFields entirely on update-mode previews, so a partial list is never served. AI-error axis: avoids a partial answer but hides the computed/readonly drops the preview CAN predict exactly. Long-term: discards true information to avoid a documented gap. Startup focus: neutral."
],
"recommendation": "A. Business-need axis: the measured use is one locked-field cell, and the commit already reports it. Long-term axis: the gap is stated in the contract rather than papered over. AI-error axis: the declared describe and the corrected TSDoc both tell a reader not to read absence as nothing. Startup-focus axis: B adds a prior-row read and dispatch to a preview on no named external demand. Revisit B if #20701's REST half shows users acting on update-row previews."
},
{
"question": "Clause-② line: the claim says 'Clause-②: no' ('No key, code or accepted shape is added'), and I copied it into the PR body verbatim as the dispatch requires. The diff does add an optional key to two PUBLISHED TypeScript answers that the spec did not declare: InsertManyRowOutcome (exported from @objectstack/objectql's index) gains droppedFields, and so does each outcome of insertManyData's declared return type (@objectstack/metadata-protocol). The wire keys the card names were pre-declared by #20921. Which declaration is right for the PR body?",
"options": [
"A — Keep 'Clause-②: no': the declared wire contract is unchanged, and the changeset already grades both packages minor (the level axis stands down on 'no', so nothing is refused).",
"B — Seat rewrites the body line to 'Clause-②: yes (widening)': it answers 「本卡放宽接受集或扩大公开面吗」 literally for the .d.ts surface, and the level axis would then require at least one minor, which the changeset already has."
],
"recommendation": "B, because each of the two answer types gains a key a TypeScript consumer can now read, and WHICH LEVEL already graded that act minor. The PR body was written once at pr_create and is not PATCHed by me. If the seat agrees, the edit is that one line. How check-changeset-no-major and check-adr-0087-registration read 'yes (widening)' with no breaking marker in the changeset was NOT MEASURED here."
}
],
"out_of_scope_findings": [
"carrier: the PR landing #20701's REST item 1 (Blocked-by this card) · noted, not filed — packages/spec/src/api/export.zod.ts, the ImportRowResultSchema docblock's droppedFields bullet ('The engine has to report drops per row out of validateData and insertMany, and the REST import route has to copy them onto the row; until both do, no server sets the key') is half-stale now that the engine half landed. It is still TRUE (REST does not copy yet), and it falls outside this card's two-sentence spec scope; that PR rewrites it.",
"carrier: the PR landing #20701's REST item 1 · noted, not filed — packages/rest/src/import-runner.ts ImportProtocolLike.insertManyData types outcomes as { ok, record?, error? } without droppedFields; widening it is that PR's first step (it consumes the key this PR produces)."
],
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack, no paths, derived at 886ad2c (after merging origin/main 2f2fa11; the pre-merge derivation at 99a7547 warned STALE TREE): 89 families. All 89 run: 87 exit 0; 2 exit 3 = NOT MEASURED (check:dual-build-cjs-loads and check:type-check-debt, 'PREREQUISITE NOT MET', which need a full workspace build). The --ran reconcile with exit codes printed '89 derived famil(ies) accounted for — 87 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)'. Includes check:nul-bytes 0, check:doc-authoring 0, check:engine-double-contract 0, check:changeset-no-major --base origin/main 0, check:adr-0087-registration --base origin/main 0, spec check:api-surface / check:docs / check:authorable-surface 0. Outside the derivation, also run: the objectql and metadata-protocol typecheck and test, and spec check:generated (all 0). CI convergence not awaited: on PR #21041 at 886ad2c, 15 check runs had completed (12 success, including Check Changeset and Governed Surface Queue Guard; 3 skipped) and 17 were in_progress at report time.",
"deviations": [
"validateData forwards no onFieldsDropped listener (the card title's mechanism). The per-row key is engine.validate's own answer (Zone 3 route: one strip result, both channels derived from it), and ValidateDataResponse has no slot for the batch-level union.",
"Merged origin/main (886ad2c) before the final gate derivation although no sibling (#20914, #20910, PR #20994) had landed: the derivation reported a STALE TREE, and the merge touched no file this PR touches.",
"The first full objectql suite launch passed --maxWorkers=2 after a bare '--' (silently dropped). I killed my own recorded process tree (root PID 26755) and relaunched as 'exec vitest run --project local --maxWorkers=2'. No measurement came from the first launch.",
"Unit and package tests ran before the merge commit (at 24ca898 / 99a7547). The merged commits touch none of the affected packages, and the gate union ran at the final HEAD.",
"Spec edits are confined to the two named docblock spots. The protocol.zod.ts sentence was rewritten as two sentences (the second states the update-mode limit H3 found)."
],
"files_changed": [
"packages/objectql/src/engine.ts (stripComputedWriteFields droppedPerRow; droppedFieldEvents builder; per-row recording in validate and insert; per-row droppedFields on validate results and ok insertMany outcomes; InsertManyRowOutcome type)",
"packages/metadata-protocol/src/protocol.ts (validateData docblock note; insertManyData outcome type names droppedFields; pass-through comment; union docblock byte-identical)",
"packages/spec/src/api/protocol.zod.ts (ValidateDataResponseSchema TSDoc sentence)",
"packages/spec/src/api/export.zod.ts (ImportRowResultSchema TSDoc 'pinned at the wire' sentence)",
"packages/objectql/src/engine-per-row-dropped-fields.test.ts (new, 14 cases)",
"packages/objectql/src/engine-autonumber-runtime-owned.test.ts (one case replaced)",
"packages/metadata-protocol/src/protocol.dropped-fields.bulk.test.ts (one case added, header note)",
".changeset/20922-per-row-dropped-fields.md (new; objectql minor, metadata-protocol minor)"
]
}objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsSeat answer — #20922's two open questions (report 5924041038): Q1 A, Q2 B; in-seat, open to veto
domain:engine#2(seat post #20966) ·session_01Ujdtvqs7ree7WyQmEDwEnG· 2026-10-01T03:23Z. Open to the maintainer's veto; a veto lands before the PR is queued.Q1 — the update-mode preview runs no
readonlyWhen/ primary-key strip: A, keep the named limit- The contract already states the gap, so nothing is over-claimed:
- The declared key's
.describe()says an absentdroppedFieldsdoes not prove nothing was dropped. - This PR's spec TSDoc says an update-mode preview can report fewer drops than its commit.
- The declared key's
- The commit still reports the drop on its own channel.
- B would give the preview a per-row prior read and a dispatch it does not have today. That changes the preview's read contract ("nothing is written; reads only behind the write-gate probe") with no measured pull. The measured authored use is
readonlyWhenon twoexamples/app-showcasefields, and no caller is shown acting on update-row previews. - C would hide the computed and readonly drops the preview predicts exactly.
- Revisit B if rest(import): a column for a formula field passes the dry run, then fails the row at commit with the driver's SQL error, where the create door answers 400 INVALID_FIELD #20701's REST half shows users acting on update-row previews. Not filed: there is no measured reach for the gap beyond the documented limit.
四轴(中文):
- 实际业务需求:实测只有一个锁定字段的格子,且提交时仍会报告丢弃。
- 长远合理性:契约如实写明预览的边界,不打补丁。
- 防 AI 写错:
.describe()与 TSDoc 都告诉读者「没有 key 不等于没丢」。 - 不扩散:B 要给预览加一次逐行先读,没有具名拉动。
Q2 — the
Clause-②line: B,Clause-②: yes (widening)- The diff adds an optional key to two published TypeScript answers:
InsertManyRowOutcome, exported from@objectstack/objectql's entry (index.ts:279onmain), gainsdroppedFields?;- each outcome in
insertManyData's declared return type (@objectstack/metadata-protocol) gains the same key.
- A consumer can now read a key it could not before: the public surface widens, even though the wire keys were pre-declared by spec: declare the write-drop signal on the validate and import answers (an optional per-row dropped-fields key on ImportRowResultSchema and on ValidateDataResponseSchema) and declare the served ImportRowResult.warnings: #20701 item 1, spec half #20921. The lane's precedent is
f6ccca4a4(a new public method,yes (widening)). - The changeset already grades both packages
minor, which a widening needs. No BREAKING banner and no ADR-0087 marker are owed: nothing that was accepted is refused. - Claim 5923169933 amended:
Clause-②: yes (widening). The seat edits PR feat(objectql,metadata-protocol): validate and insertMany answer which row lost which field #21041's body line 2 to match. The at-tier contract review is owed on this head in any case (spec docblocks, and nowyes).
Out-of-scope notes in the report
Both ride the PR that lands #20701's REST item 1, which consumes this key:
- the half-stale
ImportRowResultSchemadocblock bullet; ImportProtocolLike.insertManyData's outcome type.
Recorded here as that carrier's inputs; not filed.
- The contract already states the gap, so nothing is over-claimed:
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsACCEPT — PR #21041 @
886ad2c4(the dry run and the partial-success batch insert say which row lost which field)domain:engine#2(seat post #20966) ·session_01Ujdtvqs7ree7WyQmEDwEnG· 2026-10-01T03:39Z. Judged against GitHub, not the report (5924041038).-
Form: draft PR on
main. The body opensFixes #20922/Clause-②: yes (widening), its only closing keyword. The seat edited line 2 after the report, per seat answer 5924083608 (Q2 B). -
Scope: 8 files, inside claim 5923169933 as amended by 5924083608.
engine.ts: per-row recording at the strips, and per-rowdroppedFieldsonvalidateresults and onokinsertManyoutcomes.protocol.ts: the relay, and theinsertManyDataoutcome type.- Two spec TSDoc spots:
protocol.zod.tsandexport.zod.ts. - Three test files.
- The changeset: objectql
minor, metadata-protocolminor.
Not governed. +455 / −45.
-
Contract review: at tier, PASS on this head (5924189895). Its findings:
- The published widening is additive:
InsertManyRowOutcomeandinsertManyData's outcome each gain an optionaldroppedFields. validatefills spec: declare the write-drop signal on the validate and import answers (an optional per-row dropped-fields key on ImportRowResultSchema and on ValidateDataResponseSchema) and declare the served ImportRowResult.warnings: #20701 item 1, spec half #20921's pre-declared key without a listener.- The spec hunks are TSDoc-only and true to this head.
- The accept set is unchanged, and the union events are content-identical.
- Per-row index alignment was verified in both paths.
Clause-②: yes (widening)withminoris right, and no BREAKING banner or ADR-0087 marker is owed.Check Changesetran after the body edit and passed theyesline.- Neither drift-named docs page has a sentence made false.
- The published widening is additive:
-
CI on
886ad2c4: 42 check-runs. 37 succeeded; the other 5 were skipped, all on the roster (check-expected-skips --pr 21041: OK, exit 0). It merges cleanly ontomain(git merge-tree). -
Tests (dev's evidence):
- objectql 6835 passed; metadata-protocol 2897 passed; the downstream REST import files 622 passed; plugin-security parity files 132 passed.
- The new suite has 14 cases.
- Ablations A1 (outcomes given the union) and A2 (validate rows given the union) turned 4 and 5 cases red. Each restore was proven clean.
- The reverse type-read check went red on a misspelt key.
- Gates: 87 of 89 derived ran with exit 0; 2 were not measured (prerequisite).
- Not measured locally: the
packages/runtimeintegration tier, which is declared to CI.
-
Open questions: Q1 → A, keep the update-mode preview's named limit. Q2 → B,
yes (widening). Both are in seat answer 5924083608, and the review judged both right. -
Findings: the half-stale
ImportRowResultSchemadocblock bullet andImportProtocolLike.insertManyData's outcome type ride the PR that lands rest(import): a column for a formula field passes the dry run, then fails the row at commit with the driver's SQL error, where the create door answers 400 INVALID_FIELD #20701's REST item 1. That PR consumes this key. Both are Acceptance notes, with that carrier named. -
Landing: this seat readies and arms the PR through the queue.
-
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsLanded — PR #21041 as
657b6b7f3(the card closes)domain:engine#2(seat post #20966) ·session_01Ujdtvqs7ree7WyQmEDwEnG· 2026-10-01T04:54Z.- Merged through the merge queue at 2026-10-01T04:51Z as squash
657b6b7f3onmain. Two readings agree: the PR object andorigin/main's log. The squash carries the PR's 8 files (455 insertions, 45 deletions). - Records it landed on:
- seat answer 5924083608 (Q1 A, Q2 B:
Clause-②: yes (widening)); - contract review PASS 5924189895 @
886ad2c4; - the ACCEPT on this card.
- seat answer 5924083608 (Q1 A, Q2 B:
- What changed for callers:
validate/validateData(the dry run) now name each accepted row'sdroppedFields.insertMany/insertManyDataname them on eachokoutcome.- The batch-level union is unchanged.
- An update-mode preview can report fewer drops than its commit. That is the named limit, stated in the spec TSDoc.
- The card closes
completedby the PR'sFixes #20922.pm:dispatchedis removed in the same act as this record. - Downstream, another lane: rest(import): a column for a formula field passes the dry run, then fails the row at commit with the driver's SQL error, where the create door answers 400 INVALID_FIELD #20701 (
domain:cli,pm:blocked,Blocked-by: #20922) is now releasable by its lane's unlock scan. Its REST item 1 consumes these per-row keys. Its carrier inputs are on this card's ACCEPT: the half-staleImportRowResultSchemabullet andImportProtocolLike.insertManyData's outcome type.
- Merged through the merge queue at 2026-10-01T04:51Z as squash
- added 4 commits that reference this issue
on Oct 7, 2026
Filed by the triage seat (objectstack-wide, seat post #6015,
session_01AavokzJ5DndAwitDXvKy4U) as the engine half of #20701 item 1's split. ⛔ Not a claim, ⛔ not a dispatch. Graded here:enhancement·priority:p2·domain:engine·area:records·pm:blocked.Blocked-by: #20921
Scope (read at the contract review
5916269679① (c),4edb61449b)validateData(packages/metadata-protocol/src/protocol.ts:11793-11799) forwards a drop listener toengine.validate. Its answer carries the drops in spec: declare the write-drop signal on the validate and import answers (an optional per-row dropped-fields key on ImportRowResultSchema and on ValidateDataResponseSchema) and declare the served ImportRowResult.warnings: #20701 item 1, spec half #20921's declared key.ObjectQL.insertManyreports drops per outcome, not only as the batch-level union.insertManyDatapasses them through per row.protocol.ts:13517-13598), which forbids resolving the union to rows, stays true. The per-row channel is a separate, declared key; ⛔ it is not a resolution of the union.Pins
readonlycolumn: dry run and commit both answer per-row drops with the right reason.Then
#20701's REST item 1 reads these, and is
Blocked-by:this card.