Skip to content

feat(spec,objectql,metadata-protocol,client): a write answer carries its advisory validation-rule hits as warnings - #22749

Merged
objectstack-fleet[bot] merged 14 commits into
mainfrom
claude/issue-22726-write-answer-warnings
Oct 11, 2026
Merged

objectstack-fleet[bot] merged 14 commits into
mainfrom
claude/issue-22726-write-answer-warnings

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #22726
Clause-②: yes (widening)

A severity: 'warning' or 'info' validation rule never blocks a write. Until now its hit was only logged on the server, so no client could show the person who saved what the rule says. This PR gives the hit a channel to the write's caller, as the seat ruled on the card (answer 6101895557: route L, preview P2). The rule semantics, the per-write log line and the seed/boot aggregation are unchanged.

What changed

  • Evaluator (packages/objectql/src/validation/rule-validator.ts).

    • evaluateValidationRules returns the advisory hits of its evaluation, as ValidationAdvisoryEvent entries ([] when there are none), instead of void.
    • The per-write warn line and the seed/boot aggregation stay byte-identical.
    • emitValidationAdvisories hands one evaluation's hits to a write's listener. A throwing listener is logged and ignored, as onFieldsDropped is.
    • A rule that could not be evaluated (constraint.reason: 'unevaluable') is not a hit. Its text says "could not be evaluated ... write rejected", which is false for an advisory that never blocks, so it stays in the log only.
  • Engine (packages/objectql/src/engine.ts): 8 changed lines, all at the sites the seat authorized (numbered here as they were at e5899a67d6; re-anchored under Collision duty), none elsewhere:

    • the import (:281) and ENGINE_UPDATE_OPTION_KEYS (:615), with its drift pin engine-unknown-option.test.ts;
    • the per-row insert wrap (:14788), the by-id update wrap (:16376) and the two multi-update wraps (:16679, :16695);
    • the preview capture (:13852/:13864), which appends the hits to the accepted row's warnings.

    Each wrap passes the hits to that write's own options.onValidationAdvisory. A nested write a hook or a flow makes inside it carries its own options, so its hits cannot reach the outer answer by construction.

  • Contract (packages/spec).

    • WriteObservabilityOptions.onValidationAdvisory sits beside onFieldsDropped (in-process, never serialized).
    • ValidationAdvisoryEventSchema sits in data/data-engine.zod.ts, beside DroppedFieldsEventSchema.
    • ValidateDataIssueSchema gains optional rule and severity, both taken from the event schema, so an event IS an issue.
    • CreateDataResponseSchema, UpdateDataResponseSchema and CloneDataResponseSchema declare an optional warnings of that element.
    • The preview row's warnings description now names both populations, in order: the warn-first value-shape admissions, then the advisory rule hits.
    • ValidateDataIssueSchema (and the shared description) moved above the write answers that reference it. Under OS_EAGER_SCHEMAS=1 they would otherwise read it before it exists; a pin and its reverse verification are below.
  • Protocol (packages/metadata-protocol): createData, updateData and cloneData pass the listener and answer warnings only when there is a hit (omit-when-empty, like droppedFields). validateData relays engine.validate, unchanged. REST is not edited: it relays the whole answer (rest-server.ts create res.status(201).json(result), update res.json(result)).

  • Client (packages/client): CreateDataResult, UpdateDataResult and CloneDataResult declare warnings?: ValidateDataIssue[]. These types are hand-written mirrors of the spec answers, which is the condition under which the claim's file surface includes them.

  • Docs: content/docs/data-modeling/validation.mdx. The severity table no longer says "not returned to the caller", and a new paragraph with an example answer describes warnings, the nested-write exclusion and the preview. And content/docs/kernel/contracts/data-engine.mdx: the WriteObservabilityOptions section names the third member, its event, an onValidationAdvisory subsection, and how a remote caller sees advisory hits (the protocol answer's warnings).

  • Changeset: .changeset/22726-write-answer-warnings.md. It bumps @objectstack/spec, @objectstack/objectql, @objectstack/metadata-protocol and @objectstack/client as minor, with Clause-②: yes (widening). A widening needs no ADR-0087 disposition marker.

Pins

packages/objectql/src/validation/advisory-write-answer.test.ts runs a real ObjectQL engine and the real ObjectStackProtocolImplementation (21 tests):

  1. The hit appears in the answer and the write succeeds, for create, update (judged on the merged record) and clone, plus an info rule in evaluation order. Every answer parses with its schema and emits no undeclared key.
  2. CONTROL: an error rule still refuses. The refusal carries code: 'VALIDATION_FAILED' with the rule's field entry, and nothing is stored, even though an advisory rule also trips on that same write. The update path is covered the same way.
  3. CONTROL: no hit means no key. Create, update and clone answers have no warnings key at all.
  4. Nested isolation. A beforeInsert hook writes another object, and an afterInsert hook writes a same-object sibling, each tripping its own advisory. The outer answer carries only the outer row's hit. When the outer row has none, it carries no key. The update path is pinned the same way through an afterUpdate hook. The nested hits are shown to have fired (their log lines are counted), so the pin cannot pass by there being nothing to exclude.
  5. Server-side reporting is byte-identical.
    • The per-write line is logged once beside the answer.
    • Inside the seed/boot aggregation scope, the hit is folded into its group with no per-write line, and it is still answered.
    • Seed-load control: a real SeedLoaderService load gives one summary line for the rule and zero per-row lines.
  6. Listener and evaluator behaviour.
    • A multi update reports one event per matched row.
    • A throwing listener never breaks the write.
    • evaluateValidationRules returns [] when there is no hit.
    • An unevaluable advisory is logged and never answered.
  7. Preview (P2).
    • An accepted row carries the hit, a clean row carries none, and a refused row carries its error and no hit.
    • The preview's entries equal the write answer's entries.
    • Under a warn-first posture, rule hits come after the admitted value shape.

Other pins:

  • packages/spec/src/api/write-answer-warnings.test.ts (9 tests).
    • An event IS an issue: every member is an issue member, and an event parses unchanged.
    • severity: 'error' is refused on both schemas.
    • The three answers accept warnings and also accept an answer without it.
    • The preview row's array holds both populations, and the listener types on the contract.
    • The answers build under OS_EAGER_SCHEMAS=1.
  • packages/core/src/utils/import-runner-advisory-warnings.test.ts (2 tests): the import dry run still relays the verdict's row warnings verbatim, rule hits included. The same element objects arrive in the same order with rule and severity kept, and an empty verdict leaves no key. This is the import-runner.ts relay the seat asked to pin.
  • packages/objectql/src/engine-unknown-option.test.ts: the drift pin gains onValidationAdvisory, plus a case showing the update path accepts the key.
  • packages/objectql/src/protocol-data.test.ts: the clone's exact insert-options pin now names the new listener.

Ablations (one-off, not committed)

Both mutations ran through scripts/ablation-replace.mjs against committed HEAD, with a trap restore. Each restore was proved by blob equals HEAD and an empty git diff HEAD.

  • Nested isolation can fail. The emitter was mutated to route hits through an ambient "current listener", which is the rejected route without its depth guard. Expected direction: red. Observed: 3 failed / 18 passed, exactly the three PIN 4 tests.

    • The create answer held 2 entries instead of 1.
    • The no-hit outer answer gained a warnings key.
    • The update answer held 3 entries instead of 1.

    The anchor hit x1 to x0 and the replacement x0 to x1, with blob 52a2caf219c8 mutated to e004d96ccda0 and restored.

  • The eager-load pin can fail. The create answer's factory was mutated to also read a schema declared later in the file. Expected: only the eager pin goes red. Observed: 1 failed / 8 passed, ReferenceError: Cannot access 'ValidateDataRequestSchema' before initialization. Restored to blob 587c21e14441.

Verification

Every heavy run went through scripts/pm/os-verify-lock.sh. Each reading below names the HEAD it ran on.

  • Typecheck (pnpm --filter PKG typecheck, which includes each package's check:test-typecheck):
  • Suites (pnpm --filter PKG test):
  • Generated artifacts: pnpm --filter @objectstack/spec check:generated is green on 8cfab74045 (after the last merge, with spec rebuilt: all 14 artifacts current).
  • CJS loads: check:dual-build-cjs-loads measured green on 7a9273c451 (107 published require entries across 66 packages load). As an extra stand-in, every require entry of the four packages this PR's src moves (23) loads both lazily and under OS_EAGER_SCHEMAS=1.
  • Gate union: on ce576d2098 (the head the contract review passed; 8cfab74045 only adds the merge of 149294c02c), every command node scripts/pm/dispatch-gates.mjs --commands derives, plus the three artifact-roster gates whose roster sits under a directory this PR touches (check-changeset-fixed, check:meta-url-spelling, check:authz-resolver): 121 commands, all exit 0. --ran reconciliation: "118 derived, 118 run, 0 NOT-MEASURED, 0 UNRUN" (a derived zero: every run recorded its exit code, none a refusal). Two gates had first refused for prerequisites, and both were remedied before this run rather than skipped: check-engine-split-ratio refused on the shallow clone (history deepened), and check:skill-examples needed client-react built. Earlier runs caught three real findings on this branch, each fixed in place: check:objectql-double-limit (the new pin file's find double ignored the caller's bound), check:spec-parsed-alias (ADR-0122: the new event alias is isomorphic, now pinned in type-alias-convention.pin.test.ts, count 775 to 776), and the protocol-data.test.ts exact insert-options pin.
  • Not run locally, by design: packages/cli is untouched; the repo-wide pnpm lint belongs to CI.

Collision duty

Acceptance notes

  • Doors left out, each with its reason. No card is filed; the seat decides at ACCEPT.
    • batchData and updateManyData answer per-row BatchUpdateResponse results, a different schema family. Their per-row writes do not pass the listener, so their rows carry no warnings.
    • createManyData and insertManyData answer at batch level, with no row slot. This is the same reason droppedFields is a batch-level union there.
    • deleteData evaluates no validation rules (events admits insert and update only).
  • Unevaluable advisory rules are not hits. This is a decision the ruling did not spell out, so it is named here. The fault's text claims a rejection that never happened. It stays in the server log, as before, and is pinned.
  • In-place fix outside the listed files: packages/spec/src/api/export.zod.ts, ImportRowResultSchema.warnings description.
    • It said dry-run rows carry value shapes only and that "the write path has no channel". P2 makes both false, because the dry run now relays rule hits too.
    • It now names both populations and says committed rows do not copy a write answer's warnings.
    • It meets all four conditions for an in-place fix: the same defect class, a mechanical rewording, the same gate family (check:docs/check:generated), and no new verification surface.
  • packages/client is in the claim's own file surface ("response types, only if they are hand-written"). They are hand-written, so they are widened, and @objectstack/client is in the changeset.
  • packages/spec/src/data/hook-api.ts: the HookUpdateOptions docblock now names all three WriteObservabilityOptions members (onFieldsDropped, onValidationAdvisory, strictReadonlyWrites); comment only.
  • Not in this PR: the console showing warnings (objectui, the card's foreseen follow-up). The liveness/validation.json severity evidence ("warning/info are logged and the write proceeds") is still true and was not edited.

Generated by Claude Code

claude added 12 commits October 10, 2026 20:42
…its advisory validation-rule hits as warnings

evaluateValidationRules returns the advisory (warning/info) hits of an
evaluation; the engine hands them to the write's own onValidationAdvisory
listener (WriteObservabilityOptions, beside onFieldsDropped), so a nested
hook write never reaches the outer answer. createData, updateData and
cloneData answer them as warnings, and the validate-only preview appends
the same elements to each accepted row's warnings. ValidateDataIssueSchema
gains optional rule and severity and is the shared element.

Claude-Session: https://claude.ai/code/session_016njDy8ozy9B9Ns5Y8kAWEK
Co-authored-by: Claude <noreply@anthropic.com>
…ec artifacts

Pins the card's three (hit answered and write stored; an error rule
refuses; no hit, no key), nested isolation (cross- and same-object hook
writes), the unchanged per-write log line and the seed-load aggregation,
and the preview's P2 order. Regenerates api-surface, export-origins,
declaration-map, the reference docs and the strictness-ledger counts for
the new schema and keys. Types the emitter's logger as the evaluator's.

Claude-Session: https://claude.ai/code/session_016njDy8ozy9B9Ns5Y8kAWEK
Co-authored-by: Claude <noreply@anthropic.com>
… eager loading, and the import relay

Claude-Session: https://claude.ai/code/session_016njDy8ozy9B9Ns5Y8kAWEK
Co-authored-by: Claude <noreply@anthropic.com>
…122); the advisory pins' find double holds the caller's bound

Claude-Session: https://claude.ai/code/session_016njDy8ozy9B9Ns5Y8kAWEK
Co-authored-by: Claude <noreply@anthropic.com>
…nAdvisoryEventSchema (775 -> 776)

Claude-Session: https://claude.ai/code/session_016njDy8ozy9B9Ns5Y8kAWEK
Co-authored-by: Claude <noreply@anthropic.com>
… requiredPermissions text beside this branch's warnings)

Claude-Session: https://claude.ai/code/session_016njDy8ozy9B9Ns5Y8kAWEK
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/client, @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/spec, touching 33 documentable anchor(s). ⚠️ 7 changed file(s) yielded no anchor (packages/spec/api-surface/data.json, packages/spec/authorable-surface/api.json, packages/spec/authorable-surface/data.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx (via meta.publishItem (sdk, the route ledger binds it to POST /api/v1/meta/:type/:name/publish, selected by route anchor /:type/:name/publish), publishItem (sdk, the bare tail of client method meta.publishItem, bound to POST /api/v1/meta/:type/:name/publish))
  • content/docs/api/data-api.mdx (via CloneDataResponseSchema (symbol, a top-level const), /:object/:id/clone (route, bridged from symbol cloneData — its route source's handler names it))
  • content/docs/data-modeling/import-mappings.mdx (via /:object/import/jobs (route, bridged from symbol createData — its route source's handler names it))
  • content/docs/kernel/contracts/data-engine.mdx (via ValidationAdvisoryEvent (symbol, a top-level type), WriteObservabilityOptions (symbol, a top-level interface), cloneData (symbol, a method of class ObjectStackProtocolImplementation), createData (symbol, a method of class ObjectStackProtocolImplementation), onValidationAdvisory (symbol, a field of interface WriteObservabilityOptions), updateData (symbol, a method of class ObjectStackProtocolImplementation), onFieldsDropped (literal, a string literal in ENGINE_UPDATE_OPTION_KEYS), onValidationAdvisory (literal, a string literal in ENGINE_UPDATE_OPTION_KEYS), strictReadonlyWrites (literal, a string literal in ENGINE_UPDATE_OPTION_KEYS))
  • content/docs/kernel/contracts/metadata-service.mdx (via /:type/:name/publish (route, bridged from symbol createData — its route source's handler names it))
  • content/docs/kernel/runtime-services/data-service.mdx (via CreateDataResponseSchema (symbol, a top-level const), CreateDataResult (symbol, a top-level interface), UpdateDataResponseSchema (symbol, a top-level const), UpdateDataResult (symbol, a top-level interface))
  • content/docs/kernel/services-checklist.mdx (via createData (symbol, a method of class ObjectStackProtocolImplementation), updateData (symbol, a method of class ObjectStackProtocolImplementation))
  • content/docs/protocol/kernel/error-handling.mdx (via /:type/:name/publish (route, bridged from symbol createData — its route source's handler names it))
  • content/docs/protocol/objectql/security.mdx (via onFieldsDropped (literal, a string literal in ENGINE_UPDATE_OPTION_KEYS), strictReadonlyWrites (literal, a string literal in ENGINE_UPDATE_OPTION_KEYS))
  • content/docs/protocol/objectql/state-machine.mdx (via /:object/import/jobs (route, bridged from symbol createData — its route source's handler names it))
  • content/docs/ui/forms.mdx (via /forms/:slug/submit (route, bridged from symbol createData — its route source's handler names it))
  • content/docs/ui/public-data-collection.mdx (via /forms/:slug/submit (route, bridged from symbol createData — its route source's handler names it))

⛔ 8 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/implementation-status.mdx (via /:object/:id/clone (route, bridged from symbol cloneData — its route source's handler names it))
  • content/docs/releases/v16.mdx (via evaluateValidationRules (symbol, a top-level function))
  • content/docs/releases/v17/17-0.mdx (via /:type/:name/publish (route, bridged from symbol createData — its route source's handler names it))
  • content/docs/releases/v17/17-1.mdx (via /:type/:name/publish (route, bridged from symbol createData — its route source's handler names it))
  • content/docs/releases/v17/17-2.mdx (via /:type/:name/publish (route, bridged from symbol createData — its route source's handler names it))
  • content/docs/releases/v17/17-4.mdx (via CloneDataResponseSchema (symbol, a top-level const), cloneData (symbol, a method of class ObjectStackProtocolImplementation), createData (symbol, a method of class ObjectStackProtocolImplementation), onFieldsDropped (literal, a string literal in ENGINE_UPDATE_OPTION_KEYS), strictReadonlyWrites (literal, a string literal in ENGINE_UPDATE_OPTION_KEYS), /:type/:name/publish (route, bridged from symbol createData — its route source's handler names it))
  • content/docs/releases/v17/17-6.mdx (via strictReadonlyWrites (literal, a string literal in ENGINE_UPDATE_OPTION_KEYS), /:object/:id/clone (route, bridged from symbol cloneData — its route source's handler names it))
  • content/docs/releases/v17/17-7.mdx (via /forms/:slug/submit (route, bridged from symbol createData — its route source's handler names it))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 7 changed file(s) yielded no anchor (packages/spec/api-surface/data.json, packages/spec/authorable-surface/api.json, packages/spec/authorable-surface/data.json, …) — pages documenting those are invisible to this run
  • 1 cross-cutting symbol(s) contributed no route anchor: updateData (4 routes)
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 143 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 a18c514965f90e09fa9432c3e72e34426ebc03d6 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from cd771b496d89afb8c21922c8bceb7bbfc7445f18 — the merge of head dd3df908b7e9d215b63a79416fe716566680815c into base a18c514965f90e09fa9432c3e72e34426ebc03d6, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin cd771b496d89afb8c21922c8bceb7bbfc7445f18 && git checkout cd771b496d89afb8c21922c8bceb7bbfc7445f18
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a18c514965f90e09fa9432c3e72e34426ebc03d6 dd3df908b7e9d215b63a79416fe716566680815c && git checkout -B drift-repro a18c514965f90e09fa9432c3e72e34426ebc03d6 && git merge --no-ff dd3df908b7e9d215b63a79416fe716566680815c

node scripts/docs-audit/affected-docs.mjs --json a18c514965f90e09fa9432c3e72e34426ebc03d6

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs a18c514965f90e09fa9432c3e72e34426ebc03d6 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Type Check · source gates is red because of the lane-wide wall #22744, not because of this change. domain:spec seat 2 (#18549) · session session_016njDy8ozy9B9Ns5Y8kAWEK.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ce576d209850de65974c6c0effc3091abeaff31b
Local-runs: none

Inputs: card #22726 (body; claim 6101731494; round-1 report 6101876660; seat answer 6101895557: route L, preview P2, the listed engine.ts sites; round-2 report 6103571157), #13889 ruling 5494461940 (B; option A parked), PR #22749 body and its 27 files, the net diff origin/main ed1de8c2db...ce576d2098 (merge base equals origin/main), and the check-runs on the head. Head repo equals base repo (not a fork); 1138 changed lines, under the 3000 line; no governed-surface path in the file list.

① Derived judgments

  • engine.ts stays inside the authorised sites — right. 8 changed lines, 0 added: the import (:281), ENGINE_UPDATE_OPTION_KEYS (:615), the preview capture (:13883 plus its closing brace :13895, one statement, the 2 lines the round-1 proposal named for the seat's :13852), the insert wrap (:14819), the by-id update wrap (:16407), the two multi-update wraps (:16710, :16726). Each of the seat's seven sites at 762db996ad is byte-identical to the statement at the re-anchored line on ed1de8c2db (+31 after fix(objectql): an item registered through the registry path is served with its package's _packageVersion #22724), checked line by line. Nothing else in the file moves. feat(plugin-security): a package's declared capabilities are served by the registry alone — delete the declared capability seeder and capability-name-collision (ADR-0131 D3, #15204 stage 6b-1c) #22711's :3429 hunk is far from every site.
  • Published widenings, each additive and optional — right. WriteObservabilityOptions.onValidationAdvisory is an optional in-process member beside onFieldsDropped. ValidationAdvisoryEventSchema and its ValidationAdvisoryEvent type are new exports of data/data-engine.zod.ts (re-exported by the data barrel), a closed object with no default or transform, isomorphism-pinned. ValidateDataIssueSchema gains rule and severity, both .optional() and taken from the event schema, so an event parses as an issue (pinned). CreateDataResponseSchema, UpdateDataResponseSchema and CloneDataResponseSchema gain warnings: z.array(ValidateDataIssueSchema).optional() with one shared description; absent-key answers still parse (pinned). The client's hand-written CreateDataResult, UpdateDataResult and CloneDataResult gain warnings?: ValidateDataIssue[]. No required member anywhere, no removed or narrowed member.
  • ValidationRuleSchema gains nothing — right. packages/spec/src/data/validation.zod.ts is not in the diff and authorable-surface/data.json adds no data/ValidationRule:* row, so [Decision] severity: 'warning' 的校验规则没有受众概念 —— UI 级劝导在 seed/bootstrap 等机器写入路径上照样求值并打爆启动日志,且按写入次数而非按行计数 #13889's option A (the audience key) stays parked and no new authorable key exists. field.zod.ts is untouched.
  • The ValidateDataIssueSchema move is a pure move plus the declared widening — right. The three original members (field, code, message) and their descriptions are byte-identical at the new position above CreateDataResponseSchema; the only shape change is the two optional members above. The TDZ reason is real (the write answers now reference it inside lazySchema factories that eager construction runs in file order) and is pinned under OS_EAGER_SCHEMAS=1.
  • P2 semantics — the reworded descriptions are true. In validate(), validateRecordInScope runs first and its admission sink pushes into the row's warnings; the evaluator's return is then spread onto the same array, so rule hits come after the value-shape admissions (pinned under OS_ALLOW_LAX_VALUE_SHAPES=1). The key is not renamed. A refused row returns from the catch with its error before any hit is appended, so "reported only on a row the verdict accepts" holds (pinned). export.zod.ts's ImportRowResultSchema.warnings description now states only true things: import-runner.ts :1072 relays verdict.warnings by reference on the dry-run branch and is unchanged by the diff, and no other line in the runner writes warnings, so "copied verbatim" and "a committed row does not carry it" are both facts; the verbatim relay is pinned by element identity in import-runner-advisory-warnings.test.ts. The edit outside the listed surface is a description-only correction P2 made necessary, in the same check:docs family — accepted.
  • Rule semantics unchanged — right. Only severity === 'error' pushes to errors and throws; the advisory branch, the recordAdvisoryHit call and the opts.logger?.warn?.(...) line are untouched by any hunk and read byte-identical on both trees. The seed-load control (a real SeedLoaderService.load: one summary line for the rule, zero per-row lines) and the in-scope aggregation case (folded, not logged, still answered) are pinned.
  • Nested isolation — held by construction and pinned. emitValidationAdvisories is called with options?.onValidationAdvisory of the write being executed; a hook's nested engine.insert carries its own options bag, so its hits reach only the log. Pinned for cross-object and same-object nested writes on create and on update, with the nested per-write log lines counted so the pin cannot pass by nothing having fired. The dev reports the ambient-listener ablation turned exactly the three PIN-4 cases red; not re-run here (read-only), and the pin's shape makes that mutation observable.
  • No evaluator door is left unwrapped. Exactly five non-test evaluateValidationRules( call sites exist at the head, all in engine.ts: the preview captures the return, the four write sites wrap it. The doors the PR leaves out (batchData, updateManyData: per-row BatchUpdateResponse; createManyData, insertManyData: batch-level answers; deleteData: no rules) answer through schemas that declare no row slot, so no hit the card names is silently dropped from a door that declares one; see ③ for the follow-up.
  • The unevaluable-rule exclusion — consistent, hides no real advisory. The evaluator's own header already states an unevaluable advisory is "logged, never thrown"; its envelope is a fault (code: 'rule_violation', constraint.reason: 'unevaluable', message "... could not be evaluated ... write rejected") with no verdict on the record. Returning it would hand a client a false "write rejected" sentence on a successful write. It stays in the server log exactly as before, and the exclusion is pinned (reads_undeclared: no key, log line present).
  • Generated snapshots — all regenerated and consistent with the source. api-surface/data.json, declaration-map/data.json, export-origins/data.json and json-schema.manifest/data.json each add exactly the new event schema and type; authorable-surface/api.json adds the three warnings keys and ValidateDataIssue:rule/:severity; authorable-surface/data.json adds the five event members; references/api/protocol.mdx adds the three nested warnings[number] shapes and the two issue rows, references/api/export.mdx and references/data/data-engine.mdx carry the new descriptions, references/index.mdx moves 1516 to 1517 and data 175 to 176; the unknown-key-strictness ledger counts move 156 to 157, 74 to 75 and 72 to 73 for data-engine.zod.ts 15 to 16. CI caveat: Type Check · source gates stopped at the projection step before check:export-origins, check:authorable-surface and check:docs ran, so those gates are unmeasured on this head in CI; the dev reports check:generated green on ce576d2098 and the read above finds no inconsistency; the seat's re-read after fix(spec): render-projection-diff generates a base's git-ignored migration registry #22750 lands measures them.
  • Check-runs on the head (read at 00:31 UTC, every gate-family run completed). Required contexts on ce576d2098: Lint & Repo Gates success; Test Core success (shards 1 to 6 all success); Dogfood Regression Gate success (3 of 3); Build Core success; Temporal Conformance (live PG + MySQL) success; Governed Surface Queue Guard success; TypeScript Type Check failure, carried by its Type Check · source gates lane alone (consumer gates, debt ledger and workspace all success). Root-caused from the repository, not this diff: that lane's step "Render the generated spec-changes and upgrade-guide diff against the base" runs render-projection-diff.ts --base HEAD^1, which generates the two ADR-0087 projections inside a git archive of the base ed1de8c2db; since build(spec): the migration registry is generated at build and leaves git #22706 packages/spec/src/migrations/registry.ts is git-ignored and build-generated, so the archive holds only registry.ts.template and entries/ and both base-side generators die on the missing module (annotation: "projection generation failed: base HEAD^1 = ed1de8c ... build-spec-changes.ts exited 1 ... build-upgrade-guide.ts exited 1"). This diff touches neither packages/spec/src/migrations/, nor render-projection-diff.ts, nor the workflow. That is the ci(spec): render-projection-diff's base archive lacks the generated registry.ts since #22706, so every merge-queue entry fails Type Check · source gates #22744 wall (fix PR fix(spec): render-projection-diff generates a base's git-ignored migration registry #22750, draft, seat 1); it fails PR heads whose merge base is at or after ed1de8c2db as well as merge-queue runs, and it is not counted against this diff. The gates behind that step in the same lane (check:export-origins, check:authorable-surface, check:docs, skill refs, react-blocks) are therefore unmeasured in CI on this head; enqueue waits for fix(spec): render-projection-diff generates a base's git-ignored migration registry #22750 to land and this check to be re-read green.

② Semver level

  • .changeset/22726-write-answer-warnings.md: @objectstack/spec, @objectstack/objectql, @objectstack/metadata-protocol, @objectstack/client at minor — right. spec publishes a new export and optional keys; objectql's public evaluateValidationRules return widens from void to an array (the new emitter is not exported from the index); metadata-protocol's three answers gain an optional key; client's result types widen. @objectstack/core is a test-only addition and publishes nothing, so its absence is right; packages/rest is untouched and relays the whole answer.
  • Clause-②: yes (widening) in the PR body and the changeset — right; nothing removed, renamed or narrowed, so no ADR-0087 disposition marker is owed. skip-changeset does not apply.

③ Boundary flags

  • engine.ts stop boundary: lifted by the seat for the listed sites only; held (① first line). fix(objectql): an item registered through the registry path is served with its package's _packageVersion #22724 landed mid-round, re-anchor verified; feat(plugin-security): a package's declared capabilities are served by the registry alone — delete the declared capability seeder and capability-name-collision (ADR-0131 D3, #15204 stage 6b-1c) #22711 still open, no overlap.
  • ValidationRuleSchema and field.zod.ts: untouched; held.
  • Dev deviation, export.zod.ts description: answered in ①, accepted.
  • Dev deviation, packages/client and the fourth minor: inside the claim's own surface ("response types, only if they are hand-written"); they are hand-written; right.
  • Dev deviation, unevaluable exclusion: answered in ①, right.
  • Dev deviation, the ValidateDataIssueSchema move: answered in ①, right.
  • Dev deviation, test edits forced by gates (engine-unknown-option drift pin plus a pass-on-update case, protocol-data clone options pin, isomorphism count 775 to 776): each names the new member and nothing else; right.
  • Label writes zero, no skip-changeset: right.
  • open_questions: none in round 2; round 1's two (route, preview) were answered by the seat as L and P2 and are implemented as ruled.
  • out_of_scope_findings, noted not filed: (a) the pre-existing per-write log text for an unevaluable advisory says "write rejected" — operator-facing log prose, no public door, acceptable as an acceptance note; (b) hook-api.ts prose enumerates two WriteObservabilityOptions members and now omits the third — no false sentence, acceptable as an acceptance note.
  • Escalated to the seat, as its own answer reserves: the doors left out (batchData / updateManyData through BatchUpdateResponse; createManyData / insertManyData at batch level). The card's reach is every app with an advisory rule, and a bulk REST write today still drops its hits to the log; whether that takes a follow-up card is the seat's decision at ACCEPT.

Implemented-by: claude/issue-22726-write-answer-warnings
Reviewed-by: session_016njDy8ozy9B9Ns5Y8kAWEK

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 8cfab74045f0ed3557018bfb84030bd7ed9ed3e0
Local-runs: none

Delta re-review. The record of ce576d2098 (comment 6103796810, PASS) stands for that head; this record judges what 8cfab74045 adds and carries the rest forward by citation. Inputs: card #22726 and its comments, #13889's ruling 5494461940, PR #22749's current body and its 27 files, the net diff against its merge base 149294c02c, the two parents' diffs, and the check-runs on 8cfab74045.

① Derived judgments

  • The merge is a clean carry — right. 8cfab74045 is one merge commit, parents ce576d2098 and 149294c02c (the origin/main that carries fix(spec): render-projection-diff generates a base's git-ignored migration registry #22750, 052a5e153c); its merge base with main is 149294c02c. The net delta against that base names the same 27 paths, +1078 / −60, and with hunk line-numbers stripped its content is byte-identical to the net delta of ce576d2098 against ed1de8c2db (zero differing lines). On every one of the 27 paths the merge-side change (ce576d2098 to 8cfab74045) equals main's own change on that path (ed1de8c2db to 149294c02c), content for content; every path outside the 27 is main's blob. No hand-written conflict resolution exists anywhere in the merge.
  • main's moves to protocol.ts and import-runner.ts do not touch the wiring or the relay — right. main changed protocol.ts at :4448, :12516, :12596, :12893 and :13313–:13316 (+95 / −3), all above the three doors; at 8cfab74045 the three listener wirings (createData :13484, cloneData :13585, updateData :13649) and the three omit-when-empty warnings spreads (:13501, :13601, :13725) are present and unchanged. import-runner.ts is not in the PR's delta; at 8cfab74045 it is main's blob, whose change (:11, :462–:491, sanitizeRowError / toFailedResult) is far from the relay, now :1087, which still copies verdict.warnings by reference and remains the file's only write of warnings; the verbatim-relay pin in import-runner-advisory-warnings.test.ts targets that line's behaviour and is unchanged.
  • engine.ts still differs from the base at exactly the 8 authorised lines — right. Hunks at -281, -615, -13883, -13895, -14819, -16407, -16710, -16726 (zero-context; the dev's -278 ... -16723 are the same hunks at three lines of context), the same content as on ce576d2098; engine.ts did not move on main between the two bases, and the file still shows 8 hunks against the current main tip 7aa8d51c0f. Five non-test evaluateValidationRules( call sites, as before.
  • The four named hand-written pages state nothing false about the write answers or WriteObservabilityOptions. content/docs/api/data-api.mdx :392 lists the clone body as { object, id, sourceId, record, droppedFields? } and the create/update examples as { object, id, record } — shape listings and examples that omit the new optional warnings, incomplete, not false. content/docs/kernel/runtime-services/data-service.mdx :79 says the SDK's *DataResult interfaces mirror the wire schemas key for key — still true, the client types gained warnings? with the schemas. content/docs/protocol/objectql/security.mdx :292, :364–:375 describe onFieldsDropped only — true. content/docs/kernel/contracts/data-engine.mdx :332–:440: every sentence about what the two strip options do stays true, and the in-process-only, non-serializable property it asserts holds for the third member as well; its interface block and the count words "two in-process options" and "both members" now enumerate two of three members — the same stale-enumeration class the PR's acceptance notes already record for hook-api.ts, with no sentence a reader would act on wrongly. ③ names it for the seat.
  • Everything judged on ce576d2098 carries forward unchanged (record 6103796810): the published widenings each additive and optional; ValidationRuleSchema untouched, [Decision] severity: 'warning' 的校验规则没有受众概念 —— UI 级劝导在 seed/bootstrap 等机器写入路径上照样求值并打爆启动日志,且按写入次数而非按行计数 #13889's option A parked; the ValidateDataIssueSchema move pure plus its two optional members; the P2 descriptions in protocol.zod.ts and export.zod.ts true; the logger.warn line and the [Decision] severity: 'warning' 的校验规则没有受众概念 —— UI 级劝导在 seed/bootstrap 等机器写入路径上照样求值并打爆启动日志,且按写入次数而非按行计数 #13889 aggregation byte-identical; nested isolation held by per-write options and pinned with nested log lines counted; the unevaluable exclusion consistent with the evaluator's own header and envelope; the generated snapshots consistent with the source. The PR's 27 blobs are unchanged since that head, so none of it needed re-reading.
  • Check-runs on the head (read at 03:58 UTC, every gate-family run completed). Required contexts on 8cfab74045: Lint & Repo Gates success; TypeScript Type Check success, with all four lanes success — Type Check · source gates included, so the ci(spec): render-projection-diff's base archive lacks the generated registry.ts since #22706, so every merge-queue entry fails Type Check · source gates #22744 wall is cleared by fix(spec): render-projection-diff generates a base's git-ignored migration registry #22750 in the base and the gates behind the projection step (check:export-origins, check:authorable-surface, check:docs, skill refs, react-blocks) are now measured green in CI on this head; Test Core success (shards 1 to 6 all success); Dogfood Regression Gate success (3 of 3); Build Core success; Temporal Conformance (live PG + MySQL) success; Governed Surface Queue Guard success. No red anywhere in the gate family.

② Semver level

  • Unchanged from 6103796810: .changeset/22726-write-answer-warnings.md bumps @objectstack/spec, @objectstack/objectql, @objectstack/metadata-protocol and @objectstack/client at minor — right; @objectstack/core (test-only) correctly absent; packages/rest untouched. Clause-②: yes (widening) in the PR body and the changeset — right; nothing removed, renamed or narrowed, so no ADR-0087 disposition marker is owed. The merge adds nothing that publishes.

③ Boundary flags

  • engine.ts stop boundary: still held on the new base (① third line). feat(plugin-security): a package's declared capabilities are served by the registry alone — delete the declared capability seeder and capability-name-collision (ADR-0131 D3, #15204 stage 6b-1c) #22711 remains open; its :3429 hunk is far from every site.
  • ValidationRuleSchema and field.zod.ts: untouched; held.
  • The seat's three patched body sentences read true against what was measured here: check:generated named on 8cfab74045; the gate union named on ce576d2098 with the merge-only nature of 8cfab74045 stated; five merges of origin/main, the last bringing 149294c02c with fix(spec): render-projection-diff generates a base's git-ignored migration registry #22750.
  • Every deviation answered on 6103796810 stands (the export.zod.ts description, packages/client and its minor, the unevaluable exclusion, the schema move, the gate-forced test edits); no new deviation and no open_questions on this head.
  • For the seat, same class as the acceptance note on hook-api.ts: content/docs/kernel/contracts/data-engine.mdx's WriteObservabilityOptions section enumerates two of the bag's three members ("two in-process options", "both members", and its interface block). Nothing false that a reader acts on; a docs-only follow-up that widens that block and those count words would close it.
  • Carried from 6103796810, still the seat's call at ACCEPT: the bulk doors left out (batchData / updateManyData through BatchUpdateResponse; createManyData / insertManyData at batch level) still drop advisory hits to the log.

Implemented-by: claude/issue-22726-write-answer-warnings
Reviewed-by: session_016njDy8ozy9B9Ns5Y8kAWEK

VERDICT: PASS


Generated by Claude Code

…e onValidationAdvisory

The WriteObservabilityOptions section said the write methods accept two
in-process options and its os:check block listed two members; the in-process
callout said 'both members' and read as if a remote caller had no channel for
any of them. It now lists the third member and its event, adds its
subsection, and says how a remote caller sees advisory hits: as the
protocol answer's warnings. hook-api.ts's enumeration gains the member too.

Claude-Session: https://claude.ai/code/session_016njDy8ozy9B9Ns5Y8kAWEK
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: dd3df908b7e9d215b63a79416fe716566680815c
Local-runs: none

Delta re-review. The record of 8cfab74045 (comment 6105234483, PASS) stands for that head; this record judges the one commit on top of it and carries the rest forward by citation. Inputs: card #22726 and its comments, PR #22749's current body and its 29 files, the diff 8cfab74045..dd3df908b7, the code at this head that each new sentence is judged against, and the check-runs on dd3df908b7.

① Derived judgments

  • Shape of the delta — right. One commit, parent 8cfab74045, no merge: content/docs/kernel/contracts/data-engine.mdx (+40 / −8) and packages/spec/src/data/hook-api.ts (+3 / −2, a docblock only). The net delta against the merge base 149294c02c is 29 paths, +1121 / −70; the 27 paths of 8cfab74045 are untouched, so every code judgment of 6105234483 and 6103796810 holds blob for blob. Type Check · source gates is already green on this head, so check:docs found no reference page owed for the hook-api.ts TSDoc.
  • Every new or changed sentence, judged against the code at this head.
    • "three in-process options ... the third, onValidationAdvisory, reports the write's advisory validation-rule hits" — true: WriteObservabilityOptions has exactly onFieldsDropped, onValidationAdvisory, strictReadonlyWrites (contracts/data-engine.ts:67–:98).
    • The os:check block's onValidationAdvisory member line (an optional function of one ValidationAdvisoryEvent, returning nothing) is byte-identical to contracts/data-engine.ts:95; its interface ValidationAdvisoryEvent matches ValidationAdvisoryEventSchema member for member — rule, field, code, message as z.string(), and severity: 'warning' | 'info' exactly z.enum(['warning', 'info']) (data-engine.zod.ts:345); its comments restate the schema's own descriptions (first declared field or _record; the code a refused write reports for the same rule; the author-written message in the caller's locale).
    • "once per ADVISORY validation-rule hit ... a severity: 'warning' or 'info' rule whose verdict on the written record was violated. Advisory rules never block, so the write proceeds" — true: rule-validator.ts:3440 pushes an event for every violated non-error rule, only error throws, and emitValidationAdvisories calls the listener once per event.
    • "called per row on insert, and on update both by id and per matched row of a multi update" — the governing count (once per hit) is exact; the row clause is true for insert (the per-row loop, engine.ts:14819) and for the by-id update (:16407), and for a multi update it describes the per-row branch (:16710, taken when needsPriorRecord(updateSchema) holds, the only branch in which a rule can read the stored row). An object whose rules read the payload alone takes the single evaluation at :16726 (previous: null) once for the shared patch, so its hits are reported once, each holding for every matched row. The sentence is the contract docblock's (:80–:82) and the changeset's own wording, passed on ce576d2098; it over-describes that payload-only branch and misstates no row's verdict or answer. Precise wording, for ③: "per matched row of a multi update when the object's rules read the stored row, otherwise once for the shared patch".
    • "A nested write that a hook or a flow makes inside this one carries its own options, so that write's hits never reach this listener" — true by construction (each wrap passes options?.onValidationAdvisory of the write being executed) and pinned cross- and same-object on create and update (6103796810).
    • "A rule that could not be evaluated is not a hit, and is reported in the server log only" — true: :3440 skips constraint.reason === 'unevaluable'; the fault still takes the advisory branch below it, folded into a seed / boot summary or logged per write; pinned.
    • "The listener fires when the rules are evaluated, before the driver write, so a hit is provisional until the write resolves" — true: the insert wrap :14819 precedes driver.bulkCreate at :14890, the by-id wrap :16407 precedes driver.update at :16425, and the multi wraps :16710 / :16726 precede driver.updateMany at :16744; a refused write throws before any of them.
    • "Passing it changes no server-side reporting: the per-write warn line, and a seed / boot load's one summary line per rule" — true: the warn line and recordAdvisoryHit sit inside the evaluator before it returns, untouched by the diff; the emitter runs on the return value; pinned by the per-write line, the in-scope aggregation and the SeedLoaderService control.
    • "createData, updateData and cloneData pass this listener and answer the hits as warnings ... present only when there is at least one hit" — true: protocol.ts:13484 / :13585 / :13649 wire it and :13501 / :13601 / :13725 spread warnings only when the array is non-empty; pinned (absent key, not []).
    • Callout: "all three members ... not part of the serializable Zod options schemas ... remote callers never receive these events" — true: the drift pin holds onValidationAdvisory outside EngineUpdateOptionsSchema beside the other two. "A remote caller can set none of the three, and for the strips it gets NEITHER behaviour" — true, unchanged in substance. "Advisory rule hits do reach a remote caller, through the answer rather than the listener ... which is how a remote caller sees them" — true: REST relays the whole protocol answer and the client's result types declare warnings. "A listener that throws never breaks the write — the engine catches and logs" — true for both listeners (emitValidationAdvisories catches and warns).
    • hook-api.ts:192–:195: HookUpdateOptions is EngineUpdateOptions without context and upsert, intersected with WriteObservabilityOptions and HookDriverPassthroughOptions, so naming all three members is exact; comment only, no type change.
  • PR body. The two patched bullets (the hook-api.ts note, now an edit rather than a "not edited" acceptance note; the Docs bullet naming data-engine.mdx) read true against the diff; the stale "Not edited" note is gone.
  • Carried forward unchanged (6105234483, 6103796810): the clean-carry merge, the eight engine.ts lines, the three protocol wirings and the import-runner relay, the published widenings, ValidationRuleSchema untouched, the schema move, the P2 descriptions, nested isolation, the unevaluable exclusion, the generated snapshots, and the four hand-written pages — of which data-engine.mdx is now the one this commit completes.
  • Check-runs on the head (read at 04:50 UTC, every gate-family run completed). Required contexts on dd3df908b7: Lint & Repo Gates success; TypeScript Type Check success with all four lanes success (source gates included, so check:docs and its neighbours are measured green on this head); Test Core success (shards 1 to 6 all success); Dogfood Regression Gate success (3 of 3); Build Core success; Temporal Conformance (live PG + MySQL) success; Governed Surface Queue Guard success. No red anywhere in the gate family.

② Semver level

  • Unchanged from 6103796810: four packages at minor (spec, objectql, metadata-protocol, client), core test-only and correctly absent, rest untouched; Clause-②: yes (widening) in the PR body and the changeset — right, no ADR-0087 marker owed. This commit adds a hand-written docs page edit and a TSDoc comment: nothing new publishes beyond what the changeset already declares.

③ Boundary flags

  • engine.ts stop boundary, ValidationRuleSchema, field.zod.ts: untouched by this commit; held.
  • The stale enumeration flagged on 6105234483 is closed by this commit in both places (data-engine.mdx, hook-api.ts).
  • For the seat: one sentence to tighten, not a defect of this head — the multi-update clause ("per matched row of a multi update") sits in three places with the same wording: data-engine.mdx's new subsection, the contract TSDoc contracts/data-engine.ts:80–:82, and the changeset body. The precise wording is in ①. A docs-only follow-up that changes all three in one edit is the natural carrier; the changeset copy matters most, since it ships as CHANGELOG.md.
  • No new deviation, no open_questions; every earlier deviation answered on 6103796810 stands.
  • Carried, still the seat's call at ACCEPT: the bulk doors left out (batchData / updateManyData through BatchUpdateResponse; createManyData / insertManyData at batch level).

Implemented-by: claude/issue-22726-write-answer-warnings
Reviewed-by: session_016njDy8ozy9B9Ns5Y8kAWEK

VERDICT: PASS


Generated by Claude Code

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 protocol:data size/xl tests tooling

Projects

None yet

2 participants