docs(metadata-protocol): the create-strip aggregation states what the tree does, not a premise it falsifies - #17293
Merged
os-sam merged 1 commit intoSep 10, 2026
Conversation
… tree does, not a premise it falsifies 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. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XTBcV7zZHmokdyQgXjbyEU
Contributor
📓 Docs Drift Check
What this run could not see
Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
os-sam
marked this pull request as ready for review
September 10, 2026 04:28
os-sam
enabled auto-merge
September 10, 2026 04:28
os-sam
deleted the
claude/issue-16933-metadata-protocol-schema-uniform-prose
branch
September 10, 2026 04:53
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #16933
Clause-②: no
A comment correction. No export, schema key, closed-set member or registry entry moves, and no runtime behaviour changes.
The docblock on
mergeDroppedFieldEventsgave two reasons forcreateManyData's aggregateddroppedFields. The first is correct. The second was falsified by maintainer ruling C (#14147):The strip runs inside
engine.insert, after thebeforeInserthooks, and exempts keys a hook itself assigned — indexed per row at the call site:packages/objectql/src/engine.ts:10577handsstripReadonlyFieldsthe optionhookWrittenKeys: rowHookWrittenKeys[i], andrule-validator.ts:1268honours it withif (hookWrittenKeys?.has(name)) continue. A hook that stamps a protected key on some rows and not others makes those rows drop different sets.The shape is unchanged and stays right for the reason the producer already gave — the
{ object, records, count }response has no per-row slot, so a union is the only view it can represent. That is the same reasonpackages/speckept when the sibling carrier was corrected.Ruling 2 — the sibling, read first
PR #16930 has LANDED (merged 2026-09-08T17:47:38Z,
0bb23186), so its wording was read from the tree rather than guessed at, and this PR matches it:Deliberate difference, as the card asked: this is a partial correction, not #15777's replacement text transplanted. The card warned against copying that wording without checking it fits, and triage measured that the warning was right — the first reason was already present here, and the
#14147parenthetical ("that strip is the ENGINE's, which reports one event per CALL") is kept, because it corroborates the correction rather than fighting it: if the engine reports once per call, there is no per-row set for "uniform across rows" to be a property of.Ruling 5 — the census, by claim rather than by spelling
The claim audited is: the create-side static-
readonlystrip produces the same dropped-field set on every row of a batch — however worded. Sweeps run overpackages/metadata-protocol/**: the falsified spelling, thenuniform,every row,same set,all rows,identical,across rows,row-invariant,invariant,faithful,lossy,same fields,consistent across, then everydroppedFields/onFieldsDropped/stripcarrier read by hand.src/protocol.ts,mergeDroppedFieldEventsdocblocksrc/protocol.dropped-fields.bulk.test.ts:13, header commentsrc/protocol.dropped-fields.bulk.test.ts:90, test titlesrc/protocol.ts, same docblock's tail — "the per-rowinsertMany/batchpaths keep row precision"src/protocol.ts,createManyDatainline commentsrc/protocol.dropped-fields.bulk.test.ts:97, "both rows dropped the same readonly field"src/protocol.dropped-fields.test.ts,src/protocol.readonly-insert.test.ts,src/search-clone-schema-conformance.test.tsCHANGELOG.md:5415,:20046Zero-hit reading, with a control that fires in the same pass. After this change,
git grep -c schema-uniformoverpackages/metadata-protocol/src/**returns 0. Positive control on the same paths in the same pass:mergeDroppedFieldEvents→ 6,droppedFields→ 78. The zero is a reading, not a dead instrument.Two carriers of the same premise sit outside this package and are filed rather than reached across into — see acceptance notes.
Ruling 3 — region fence, re-measured rather than trusted
Re-measured against PR #17252's live file list rather than the dispatch sentence. It touches
packages/metadata-protocol/src/protocol.tsat hunks@@ -4827,@@ -4912,@@ -5003,@@ -5033(region 4820-5100) and, in this package, onlyprotocol.runtime-gate-stored-universe.test.tsbesides. This PR's sites areprotocol.ts:1877andprotocol.dropped-fields.bulk.test.ts— about 2,940 lines clear of that region, no file overlap.Ruling 4 — the grade, measured
skip-changeset, and the reasoning rather than the assumption. The sibling PR found the hypothesis "a comment-only edit publishes nothing" is false forpackages/spec, which shipssrc/**/*.zod.ts. So it was measured here instead of inherited:packages/metadata-protocolfiles[]is["dist", "README.md", "CHANGELOG.md"]— sources are not shipped.pnpm --filter @objectstack/metadata-protocol build), then the wholefiles[]surface grepped:AT LEAST ONE row dropped this field→ 0,rowHookWrittenKeys→ 0,two rows forging the same readonly key→ 0.mergeDroppedFieldEvents→ 2 dist files,droppedFields→ 4,createManyData→ 4. The instrument fires.distat all (Collapse a batch→ 0,no per-row slot→ 0), andmergeDroppedFieldEventsis module-private, so it appears indist/index.d.ts0 times.schema-uniformhit insidefiles[]isCHANGELOG.md— compiled release history, never back-edited.Verification
Commands run, exit codes captured by redirect before any pipe, verdicts read from each gate's own line.
pnpm --filter '@objectstack/metadata-protocol^...' build⇒VERDICT command-exit 0.pnpm --filter @objectstack/metadata-protocol test⇒Test Files 172 passed | 2 skipped (174),Tests 2473 passed | 10 skipped (2483),VERDICT command-exit 0. Nothing was skipped, quarantined or.todo-ed by this change; those counts are the tree's.pnpm --filter @objectstack/metadata-protocol typecheck⇒VERDICT command-exit 0. It genuinely covers the edited test:tsc --noEmit --listFilesnamesprotocol.dropped-fields.bulk.test.ts.scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, not hand-listed. 54 derived, 54 run; reconciled with--ran⇒ "54 derived famil(ies) accounted for — 54 run, 0 NOT-MEASURED, 0 UNRUN." 51 exit 0.check:lean-entry-closurewas recovered by building@objectstack/objectqland now passes on a real measurement (2 published conditions, 15 packages). The other two need a whole-farm build and are CI's:check:dual-build-cjs-loads(57 packages have nodist) andcheck:type-check-debt(18 dependency closures unbuilt — its own text refuses to let a ledger number be read from that state).eslint.config.mjsthat never enables type-aware linting (noparserOptions.project, no typed rules) for any file —eslint.config.mjs:324-334. ② The count is read from--format json: 2 files linted,errorCount 0,warningCount 0, exit 0 — exactly the 2 files this PR changes. ③ Invariance: with no type-aware rules, a file's verdict is a function of its own bytes plus the shared config, and this diff changes neither the config nor any other file, so no untouched file's verdict can move. The repo-widepnpm lintis CI's run.check:nul-bytesexit 0, plus a direct sweep of both edited files for the non-NUL control range: no hits.Acceptance notes
Out of scope, filed: #17290 — a behaviour defect the by-claim census turned up.
insertManyDatacarries the converse claim ("the strip only removes keys the ROW ITSELF supplied, so a dropped name belongs to exactly the rows whose supplied payload carried it") and, unlike the sentence this PR corrects, the code implements it:const mine = [...engineDropped].filter((f) => f in supplied). The same per-row hook exemption falsifies it — a row whosebeforeInserthook re-assigned the protected key keeps it, yet still gets adroppedFieldsentry naming it, so the response reports a field as dropped on an outcome whose record still carries it. Not repaired here: ruling 1 fences this PR to prose, and the seat'sClause-②: nowas declared for a comment edit. Its pin does not catch it either — the engine double there has no hook-write concept, so it is green through exactly the shape it pins. The prose at that site is deliberately left untouched: it accurately describes what the code does, and correcting only the comment would document the defect as intent.Out of scope, filed: #17291 — the last two live source carriers of the same premise, in another package.
packages/objectql/src/engine.ts:10614andpackages/objectql/src/validation/rule-validator.ts:1592both justify a per-call warning aggregation with "the strip is schema-uniform". The second is the sharper one: the same file states the opposite granularity at:1411— "Per ROW, never per call" — about 180 lines above. Not touched here:packages/objectqlis a different package and lane, and the dispatch fences this card topackages/metadata-protocol.Noted, not filed: the aggregate shape itself is not questioned. Ruling 1 invited a report if the aggregated
droppedFieldsshape were wrong. It is not —createManyData's response has no per-row slot, so a union is the only view it can represent, and that reason survives ruling C untouched. Successor for this note: whoever takes #17290, since that card decides what the per-row faces owe; recorded here so it is not re-derived.Noted, not filed: carrier 4's narrowing, stated so a reviewer can object to it. "The per-row
insertMany/batchpaths keep row precision instead" is true ofbatch/updateMany, where the engine reports per row, and an over-claim forinsertMany, where precision is reconstructed by the inference #17290 falsifies. Rather than leave a mixed claim standing in a docblock this PR is correcting for over-claims, or restate a claim whose card is undecided, it is narrowed to the part that is uncontested: those paths have a per-row result to hang adroppedFieldson. No card is cited in the comment — the card asked that replacement prose state what is true today and cite the artefact, never a schedule.Dedup for both filings. Run on the
findinglabel,state=all, four pages of 100 read through repo-scoped REST and grepped locally, with the positive control firing in the same pass (#16933 matchedschema-uniformanddroppedFields; #15777 matched too). Zero hits forinsertManyData,hookWrittenKeysorrowHookWrittenKeyson any open card. The closest closed neighbour, #15819, enumerated seven non-published sites of the superseded readonly-on-INSERT class and returns 0 forschema-uniform; its onerule-validator.tsrow is a different site.Channel declaration. Card, comments, sibling PR, region-fence file list and the dedup listing were all read through repo-scoped REST (probe returned 200). The
/search/*path is refused by the egress proxy by design (403, "sessions are bound to their configured repositories"), so dedup used REST list endpoints plus local grep rather than a semantic search, and no MCP GitHub call was made in this run.Attribution-form conflict, declared rather than silently resolved. The harness-supplied commit trailer carries a model identifier, while AGENTS.md requires the trailer pair to stay model-free. AGENTS.md resolves this itself: a harness-written
Co-Authored-Bytrailer is an explicit reporting exemption and is not a declared deviation. The trailer is used as the harness supplied it; no model identifier appears anywhere else — not in this body, the title, the filed cards or any code comment.Generated by Claude Code