Repository navigation
fix(objectql): an update carrying a stored reference back unchanged is not refused because it stopped resolving (#29062) - #29245
Conversation
…he row (#29062) — red on base Claude-Session: https://claude.ai/code/session_01ADHkmSDzgkXapyAsvrXkrV Co-authored-by: Claude <noreply@anthropic.com>
…s, so a stored deleted-user id carried back unchanged passes (#29062) Claude-Session: https://claude.ai/code/session_01ADHkmSDzgkXapyAsvrXkrV Co-authored-by: Claude <noreply@anthropic.com>
…oes not already store; changeset (#29062) Claude-Session: https://claude.ai/code/session_01ADHkmSDzgkXapyAsvrXkrV Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67 席位核对 · ACCEPT · PR #29245 · 2026-11-02T10:20ZHead-sha: 维护者速读(席位终稿)
核对六项
|
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 20 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 97e6c798e8eebc6ac3502ef9ecb20e4f6d4becf5 && git checkout 97e6c798e8eebc6ac3502ef9ecb20e4f6d4becf5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 01252a451f06cc85379ce5496ad2e566eea121d3 b330269165da27ee3df2f45ce91fdb80af333fac && git checkout -B drift-repro 01252a451f06cc85379ce5496ad2e566eea121d3 && git merge --no-ff b330269165da27ee3df2f45ce91fdb80af333fac
node scripts/docs-audit/affected-docs.mjs --json 01252a451f06cc85379ce5496ad2e566eea121d3
|
…alog and fields page (#29062) Claude-Session: https://claude.ai/code/session_01ADHkmSDzgkXapyAsvrXkrV Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67 席位核对 · ACCEPT(补记新 head)· PR #29245 · 2026-11-02T10:38ZHead-sha: 在 ACCEPT
席位读 diff:2 个文件 +9/-4,未碰 |
Requested by Steve · project thread
Fixes #29062
Clause-②: yes (widening)
Metadata-protocol: none
维护者速读
What changes
assertReferencesResolve(the write-timereference_not_foundcheck) gains an optionalstoredRowsreader: the rows the write lands on, as stored before it. After the probes run, a refused value (or a refused member of a multi-valued field) that every one of those rows already stores is dropped from the failures: it is not a reference this write adds. The stored rows are read only once a probe has refused, so a write whose references all resolve reads nothing new.Every call site of
this.assertReferencesResolve(and what it now does:engine.ts, the per-row admit loop)engine.ts, after the validation rules)priorRecord, always read there (the not-found gate)multi: true(engine.ts, the bulk call site)readPriorRowsmemo (the matched rows), read only after a refusalengine-validate.ts)insertmode only, so unchangedupdate.options.upsertis retired), so there is no fourth siteThe delete walk is untouched:
DELETE_SEMANTICS_REFERENCE_TYPESstill leavesuserout by declaration (moving it is a spec change).Route B covers the single
userfield as well as the multi-valued one. The previous measurement on this card (os-dev-report 6571331581) showed the single field refused the same way: a PATCH carryingf_userunchanged after the user was removed answered 400reference_not_found. The rule is not special-cased by type, so it applies tolookupandmaster_detailthe same way: a stored value carried back unchanged answers what omitting it answers.Tests
New
describe('[#29062] …')inpackages/objectql/src/engine-lookup-referential-integrity.test.ts, reusing that file's stub driver. The fixture deletes asys_userrow throughengine.deleteafter a holder row names it, so the stored ghost is the one the delete walk really leaves:userfield carried back unchanged passes;userfield carried back unchanged passes;userfield keeping its stored ghost and adding a new ghost refuses only the new one (fieldslength 1,value: u_never), and the stored value does not move;userfield repointed at another missing user is still refused;lookup(single and multi) that adds a missing id is still refused, member by member;Forced test edit:
packages/objectql/src/engine-unreadable-master-detail-header.test.ts, the%s — a refused read of the header object answers as a nonexistent header idcase. For therepoint by idandbulk repointdoors it wroteh_mine_open, which is the header linel1already stores (itsbeforeEach), so the write was not a repoint at all and is now forgiven like an omitted key. The case now namesh_mine_locked, another existing header Carol cannot read, so all four doors still pin "an existing unreadable header answers what a nonexistent one does". Nothing else in that file moved; 30/30 pass.Acceptance notes
deleteBehavioron auserfield is declared but not enforced.master_detailheader the caller cannot read, carried back unchanged, now passes (it is the value the row stores, and omitting the key already passed). A repoint to an unreadable header is still refused as a nonexistent one.Verification
Red first on base 2af0ae2
vitest run src/engine-lookup-referential-integrity.test.tswith only the new tests committed:Tests 4 failed | 17 passed (21). Failing: single unchanged, multi unchanged, stored ghost plus new ghost (expected [...] to have a length of 1 but got 2), bulk. Cause line:ValidationError: Owner: no sys_user record has id "u_gone".Ablations (scripts/ablation-replace.mjs, restored blob == HEAD, git diff HEAD empty)
const stored = [];in place of the stored-row read):Tests 4 failed | 17 passed (21), the same four cases.everymatched row changed tosomematched row:Tests 1 failed | 20 passed (21), only the bulk case.Tests and gates on the final head 49f236e (origin/main merged twice; the second merge brings #29060)
pnpm --filter @objectstack/objectql test:Test Files 527 passed (527),Tests 10145 passed (10145);pnpm --filter @objectstack/objectql typecheck: exit 0 (lock VERDICT command-exit 0).service-automationrunas-system-reference-check.integration.test.ts+builtin/crud-runas.test.ts:Test Files 2 passed (2),Tests 44 passed (44). The flow-write reference check fix(objectql)!: a runAs system flow's record writes keep the reference check while keeping their authorization elevation (#28881) #29060 added and this change compose: a flow repointing a field at a deleted user is still refused; a stored value carried back is forgiven on either caller.node scripts/pm/dispatch-gates.mjs --commands(no paths): 63 families derived; all 63 run with exit codes recorded;--ran: 62 run, 1 NOT MEASURED. The one isnode scripts/check-plugin-teardown-shape.mjs --self-test, exit 3 PREREQUISITE NOT MET (its positive control is pinned to a commit this shallow clone cannot reach); declared to CI'sLint & Repo Gates.pnpm lint: exit 0.Generated by Claude Code