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
The readonlyWhen strip and the insert-side runtime-owned strip still decide hook-vs-caller by Object.is, the comparison #14088 retired one function over #14259
Found while implementing #14088 (PR #14258), on origin/main at 66ecc50a. Deliberately left alone there — #14088's scope is the static readonly strip, and one of the two seams below is a state LOCK whose relaxation is a decision, not a mechanical follow-through.
The defect
#14088 replaced Object.is(payload[name], supplied[name]) in stripReadonlyFields with a record of the keys the before-phase hook chain actually assigned (recordHookPayloadWrites, packages/objectql/src/hook-write-provenance.ts). The argument for that was not about null: value equality cannot separate the hook deliberately wrote the value the caller also sent from the hook never touched the key, and those demand opposite verdicts.
Two sibling seams still use the comparison that argument retired, and both are in the same file:
A third, narrower seam is worth naming in the same breath: the #14088 recorder is armed at hookContext construction, i.e. after the middleware chain, so a MIDDLEWARE stamp that writes the value the caller also sent is still judged by value equality. Arming earlier is a one-line move with the same forgery argument, but it widens what the record covers and deserves its own measurement.
Seam 2 is a different code path (engine.insert's per-row hook contexts) with its own fixtures — a new verification surface, which is exactly the boundary the in-place-fix exemption draws.
The constraint any repair inherits
⛔ The forgery boundary from #14088 applies unchanged and is the part where a mistake is worse than the bug: a caller-supplied value must never become hook-owned. The record is safe only because it is armed after the caller's payload has arrived and sealed before any engine-owned pass touches it, and because it records that an assignment ran rather than anything about the payload's contents. Any producer of a hook-written key set owes the same proof.
⛔ And it is not a relaxation: a caller-supplied value that no hook wrote must still be stripped, still warn with the same text, and still report through onFieldsDropped / strictReadonlyWrites.
Reproduction shape
The same one #14088 uses, retargeted: a hook that writes a locked/runtime-owned field the value the caller also sent, and the negative control of the identical caller payload with no hook write, which must reach the opposite verdict.
Found while implementing #14088 (PR #14258), on
origin/mainat66ecc50a. Deliberately left alone there — #14088's scope is the staticreadonlystrip, and one of the two seams below is a state LOCK whose relaxation is a decision, not a mechanical follow-through.The defect
#14088 replaced
Object.is(payload[name], supplied[name])instripReadonlyFieldswith a record of the keys the before-phase hook chain actually assigned (recordHookPayloadWrites,packages/objectql/src/hook-write-provenance.ts). The argument for that was not aboutnull: value equality cannot separate the hook deliberately wrote the value the caller also sent from the hook never touched the key, and those demand opposite verdicts.Two sibling seams still use the comparison that argument retired, and both are in the same file:
isCallerSuppliedValue(packages/objectql/src/validation/rule-validator.ts) — the shared predicate behindstripReadonlyWhenFieldsandstripReadonlyWhenFieldsMulti. Its own docblock says it is written "to be textually parallel with the same test insidestripReadonlyFields" so the two "can never disagree about what caller-supplied means". AfterstripReadonlyFieldsusesObject.isto tell a hook write from a caller write, so a hook cannot clear a readonly field the caller also sent as null #14088 they do disagree. AbeforeUpdatehook deriving areadonlyWhen-locked field loses its write to any caller that echoed the same value back — the [objectql] TRUE readonlyWhen strips beforeUpdate-derived values too — a conditionally-locked derived field has no server-side write path at all (isSystem included), unlike static readonly's hook-stamp protection #9107 defect, surviving on the one input [objectql] TRUE readonlyWhen strips beforeUpdate-derived values too — a conditionally-locked derived field has no server-side write path at all (isSystem included), unlike static readonly's hook-stamp protection #9107's fix cannot see.stripRuntimeOwnedFields(same file, the INSERT-side twin). insert 路径 stripRuntimeOwnedFields 同样删的是 hook 覆写后的当前值 —— beforeInsert hook 写入的 autonumber 在调用方也提交了同名键时被连坐删除(与其自身文档矛盾) #6339's own prose is the finding: it argued the key-SET judgement "made that sentence true only by accident" and moved to values, which is accidental in the identical way. AbeforeInserthook re-issuing or normalising a record number loses its write when the caller happened to submit the same value.A third, narrower seam is worth naming in the same breath: the #14088 recorder is armed at
hookContextconstruction, i.e. after the middleware chain, so a MIDDLEWARE stamp that writes the value the caller also sent is still judged by value equality. Arming earlier is a one-line move with the same forgery argument, but it widens what the record covers and deserves its own measurement.Why it was not folded into #14088
readonlyWhenpredicate in a case where today it does not, and Parent-scopedreadonlyWhenis unenforced server-side — the field lock fails open, so a paid invoice's frozen lines can be rewritten over the API #4889's frozen paid-invoice lines are built on that lock. That is a maintainer-shaped call, not a mechanical follow-through, and it should not ride in on a p1 corruption fix.engine.insert's per-row hook contexts) with its own fixtures — a new verification surface, which is exactly the boundary the in-place-fix exemption draws.The constraint any repair inherits
⛔ The forgery boundary from #14088 applies unchanged and is the part where a mistake is worse than the bug: a caller-supplied value must never become hook-owned. The record is safe only because it is armed after the caller's payload has arrived and sealed before any engine-owned pass touches it, and because it records that an assignment ran rather than anything about the payload's contents. Any producer of a hook-written key set owes the same proof.
⛔ And it is not a relaxation: a caller-supplied value that no hook wrote must still be stripped, still warn with the same text, and still report through
onFieldsDropped/strictReadonlyWrites.Reproduction shape
The same one #14088 uses, retargeted: a hook that writes a locked/runtime-owned field the value the caller also sent, and the negative control of the identical caller payload with no hook write, which must reach the opposite verdict.
Unassigned and untriaged.
Generated by Claude Code