Skip to content

docs(metadata-protocol): the create-strip aggregation states what the tree does, not a premise it falsifies - #17293

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-16933-metadata-protocol-schema-uniform-prose
Sep 10, 2026
Merged

docs(metadata-protocol): the create-strip aggregation states what the tree does, not a premise it falsifies#17293
os-sam merged 1 commit into
mainfrom
claude/issue-16933-metadata-protocol-schema-uniform-prose

Conversation

@claude

@claude claude Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

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 mergeDroppedFieldEvents gave two reasons for createManyData's aggregated droppedFields. The first is correct. The second was falsified by maintainer ruling C (#14147):

The create-side static-readonly strip is schema-uniform — every row drops the same set — which makes an aggregated view faithful rather than lossy.

The strip runs inside engine.insert, after the beforeInsert hooks, and exempts keys a hook itself assigned — indexed per row at the call site: packages/objectql/src/engine.ts:10577 hands stripReadonlyFields the option hookWrittenKeys: rowHookWrittenKeys[i], and rule-validator.ts:1268 honours it with if (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 reason packages/spec kept 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:

  • same surviving reason (no per-row slot ⇒ a union is the only representable view);
  • same reader guidance, which is the half that actually changes for a reader — read a name as "at least one row dropped this field", never "every row dropped the same set";
  • same evidence for the falsification (the strip is after the hooks; the hook exemption is tracked per row).

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 #14147 parenthetical ("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-readonly strip produces the same dropped-field set on every row of a batch — however worded. Sweeps run over packages/metadata-protocol/**: the falsified spelling, then uniform, every row, same set, all rows, identical, across rows, row-invariant, invariant, faithful, lossy, same fields, consistent across, then every droppedFields / onFieldsDropped / strip carrier read by hand.

# site verdict
1 src/protocol.ts, mergeDroppedFieldEvents docblock asserts the claim ⇒ corrected (the card's site)
2 src/protocol.dropped-fields.bulk.test.ts:13, header comment asserts the claim ⇒ corrected
3 src/protocol.dropped-fields.bulk.test.ts:90, test title named the pinned behaviour by the falsified premise ⇒ renamed, assertions untouched
4 src/protocol.ts, same docblock's tail — "the per-row insertMany/batch paths keep row precision" over-claim of the same family ⇒ narrowed to the structural fact (they have a per-row result to hang one on). See acceptance notes.
5 src/protocol.ts, createManyData inline comment gives only the correct reason ("no per-row slot, so a union is the only representable view") ⇒ true, left alone
6 src/protocol.dropped-fields.bulk.test.ts:97, "both rows dropped the same readonly field" scoped to that fixture, where it is true by construction ⇒ left alone
7 src/protocol.dropped-fields.test.ts, src/protocol.readonly-insert.test.ts, src/search-clone-schema-conformance.test.ts delegation and wire-shape prose, no uniformity claim ⇒ left alone
8 CHANGELOG.md:5415, :20046 compiled release history ⇒ never back-edited

Zero-hit reading, with a control that fires in the same pass. After this change, git grep -c schema-uniform over packages/metadata-protocol/src/** returns 0. Positive control on the same paths in the same pass: mergeDroppedFieldEvents6, droppedFields78. 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.ts at hunks @@ -4827, @@ -4912, @@ -5003, @@ -5033 (region 4820-5100) and, in this package, only protocol.runtime-gate-stored-universe.test.ts besides. This PR's sites are protocol.ts:1877 and protocol.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 for packages/spec, which ships src/**/*.zod.ts. So it was measured here instead of inherited:

  • packages/metadata-protocol files[] is ["dist", "README.md", "CHANGELOG.md"] — sources are not shipped.
  • Built (pnpm --filter @objectstack/metadata-protocol build), then the whole files[] surface grepped: AT LEAST ONE row dropped this field → 0, rowHookWrittenKeys → 0, two rows forging the same readonly key → 0.
  • Positive control on the same grep: mergeDroppedFieldEvents → 2 dist files, droppedFields → 4, createManyData → 4. The instrument fires.
  • Mechanism, so the zero is explained and not just observed: no docblock prose reaches dist at all (Collapse a batch → 0, no per-row slot → 0), and mergeDroppedFieldEvents is module-private, so it appears in dist/index.d.ts 0 times.
  • The one schema-uniform hit inside files[] is CHANGELOG.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.

  • Dependency closure buildpnpm --filter '@objectstack/metadata-protocol^...' buildVERDICT command-exit 0.
  • Affected package, full suitepnpm --filter @objectstack/metadata-protocol testTest 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.
  • Typecheckpnpm --filter @objectstack/metadata-protocol typecheckVERDICT command-exit 0. It genuinely covers the edited test: tsc --noEmit --listFiles names protocol.dropped-fields.bulk.test.ts.
  • Gate families — derived from the real change set with 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.
  • 3 gates are NOT MEASURED, not passes — all three exit 3 = PREREQUISITE NOT MET, all one cause: they load built output this targeted build does not produce. check:lean-entry-closure was recovered by building @objectstack/objectql and 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 no dist) and check:type-check-debt (18 dependency closures unbuilt — its own text refuses to let a ledger number be read from that state).
  • Lint — a narrowed run, delivered as a measurement with its three pieces. ① The population is read from eslint's own config, which states it and which a grep confirms: this repo runs one eslint.config.mjs that never enables type-aware linting (no parserOptions.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-wide pnpm lint is CI's run.
  • Control characterscheck:nul-bytes exit 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. insertManyData carries 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 whose beforeInsert hook re-assigned the protected key keeps it, yet still gets a droppedFields entry 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's Clause-②: no was 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:10614 and packages/objectql/src/validation/rule-validator.ts:1592 both 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/objectql is a different package and lane, and the dispatch fences this card to packages/metadata-protocol.

Noted, not filed: the aggregate shape itself is not questioned. Ruling 1 invited a report if the aggregated droppedFields shape 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/batch paths keep row precision instead" is true of batch/updateMany, where the engine reports per row, and an over-claim for insertMany, 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 a droppedFields on. 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 finding label, state=all, four pages of 100 read through repo-scoped REST and grepped locally, with the positive control firing in the same pass (#16933 matched schema-uniform and droppedFields; #15777 matched too). Zero hits for insertManyData, hookWrittenKeys or rowHookWrittenKeys on any open card. The closest closed neighbour, #15819, enumerated seven non-published sites of the superseded readonly-on-INSERT class and returns 0 for schema-uniform; its one rule-validator.ts row 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-By trailer 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

… 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
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/metadata-protocol/src/protocol.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/metadata-protocol/src/protocol.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json ab450f49a154859630dee81aeb1ba86d9665100fpackageMentionDocs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants