You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Found while measuring the #14744 census (PR #15301); ⛔ not fixed there — out of that card's measurement-only scope. Unassigned and untriaged.
What is stale
Three provenance-stamp beforeUpdate hooks carry the same two comments, and runtime measurement falsifies both:
packages/plugins/plugin-email/src/email-template-provenance.ts:25 and :65
packages/plugins/plugin-sharing/src/sharing-rule-provenance.ts:25 and :53
packages/plugins/plugin-webhooks/src/webhook-provenance.ts:24 and :56
Claim 1 — "Known boundary: multi-row updates (no single input.id) are not stamped". False since #6966. Per-row before* dispatch binds ctx.input.id on every context, so the if (!id) return guard no longer detects a bulk write — the hook runs once per matched row. #6966 is exactly this defect and was fixed in service-storage's file-reference-lifecycle.ts (which now asks perRowDispatch(ctx), the engine's marker); these three were not carried along.
Claim 2 — "previous is not resolved before beforeUpdate hooks run — read the current row ourselves". False since #5574 / #5846: the engine binds hookContext.previous before dispatching beforeUpdate on every write shape, including each per-row context of a predicate write. #6656 retired plugin-audit's captureBefore for precisely this reason. Each of these three still issues its own engine.find(...) per row — a second read of a row the engine has already read.
Measured
With the real engine (packages/objectql/src/engine.ts) and the real bind functions, two rows updated in one multi: true call:
rows
observed
disagree on managed_by
row a writes customized: true, row b writes nothing ⇒ the batch is refused: MULTI_UPDATE_HOOK_KEY_DIVERGENCE, 400, keys: ['customized'], rows: 2
agree on managed_by
both rows write customized: true; one SET clause; the write lands
So the documented "not stamped on multi-row updates" boundary is not what happens: the hooks fire per row, and since #14099 landed they can now refuse an operator's bulk edit of package-managed rows. That refusal is correct engine behaviour — the problem is that these hooks' own contract says they take no part in multi-row updates, so neither their authors nor their callers expect it.
⛔ Not a per-row-value instance — the value written is the constant true, so this is not#14744's residue and the census counts it as a non-instance. It is the residue of two closed cards whose remediation did not reach these three files:
Ask the engine's marker rather than the shape of input.id — perRowDispatch(ctx), as file-reference-lifecycle.ts does — and read ctx.previous instead of re-reading the row. Whether these hooks should stamp per-row at all, or decline on a predicate write, is a product question and is left open here.
Found while measuring the #14744 census (PR #15301); ⛔ not fixed there — out of that card's measurement-only scope. Unassigned and untriaged.
What is stale
Three provenance-stamp
beforeUpdatehooks carry the same two comments, and runtime measurement falsifies both:packages/plugins/plugin-email/src/email-template-provenance.ts:25and:65packages/plugins/plugin-sharing/src/sharing-rule-provenance.ts:25and:53packages/plugins/plugin-webhooks/src/webhook-provenance.ts:24and:56Claim 1 — "Known boundary: multi-row updates (no single
input.id) are not stamped". False since #6966. Per-rowbefore*dispatch bindsctx.input.idon every context, so theif (!id) returnguard no longer detects a bulk write — the hook runs once per matched row. #6966 is exactly this defect and was fixed inservice-storage'sfile-reference-lifecycle.ts(which now asksperRowDispatch(ctx), the engine's marker); these three were not carried along.Claim 2 — "
previousis not resolved before beforeUpdate hooks run — read the current row ourselves". False since #5574 / #5846: the engine bindshookContext.previousbefore dispatchingbeforeUpdateon every write shape, including each per-row context of a predicate write. #6656 retiredplugin-audit'scaptureBeforefor precisely this reason. Each of these three still issues its ownengine.find(...)per row — a second read of a row the engine has already read.Measured
With the real engine (
packages/objectql/src/engine.ts) and the real bind functions, two rows updated in onemulti: truecall:managed_byawritescustomized: true, rowbwrites nothing ⇒ the batch is refused:MULTI_UPDATE_HOOK_KEY_DIVERGENCE,400,keys: ['customized'],rows: 2managed_bycustomized: true; oneSETclause; the write landsSo the documented "not stamped on multi-row updates" boundary is not what happens: the hooks fire per row, and since #14099 landed they can now refuse an operator's bulk edit of package-managed rows. That refusal is correct engine behaviour — the problem is that these hooks' own contract says they take no part in multi-row updates, so neither their authors nor their callers expect it.
Reproduce with the census probe on PR #15301:
Why this is worth a card
⛔ Not a per-row-value instance — the value written is the constant
true, so this is not #14744's residue and the census counts it as a non-instance. It is the residue of two closed cards whose remediation did not reach these three files:before*dispatch bindsinput.idon every context, silently changing the semantics of every "no id ⇒ skip, this is a bulk write" hook guard #6966), andfindper matched row.Suggested shape (not a decision)
Ask the engine's marker rather than the shape of
input.id—perRowDispatch(ctx), asfile-reference-lifecycle.tsdoes — and readctx.previousinstead of re-reading the row. Whether these hooks should stamp per-row at all, or decline on a predicate write, is a product question and is left open here.Generated by Claude Code