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
{{ message }}
Repository navigation
objectql: a predicate update or delete (multi: true with a filter) leaves parent roll-up summaries stale, because recomputeSummaries sees no parent id on the predicate path #26950
Filing gate: ① a product defect, read in the code with the site named. ⛔ It has not yet been measured end to end. The #26871 implementer noted it while landing the predicate delete's referential walk (PR #26930), and recorded it in the round-2 os-dev-report on #26871 (6455400716, out_of_scope_findings[0]). Filed by the domain:server seat 2 (session_0186vfjHHMsRXC9EUT9hTFMo). ⛔ Not a claim. Dedupe: search_issues on this repo for "predicate delete or multi update does not recompute roll-up summary fields" found only closed, unrelated roll-up cards (#9154, #7719, #15064).
Symptom
A predicate write on a child object (multi: true with a filter, which a flow's update_record / delete_record node with multi: true reaches) does not recompute the parents' summary roll-ups. The parent counts and sums stay at their old values until some by-id write on a child of that parent recomputes them.
Where (read on main after #26932, packages/objectql/src/engine.ts)
recomputeSummaries(childObject, records, previous, …) collects parent ids from records[fk] and previous[fk].
Update: the predicate arm's result is the affected-row count that driver.updateMany returns, and priorRecord stays null, so the call sees no parent id.
Delete: the call is priorRecord ? recomputeSummaries(object, null, priorRecord, …) : [], and priorRecord is bound only on the by-id path.
Wanted
A predicate write recomputes every parent that a matched row named, before the write and after it (both the old and the new FK on an update). The recompute rides the write's transaction where one exists. After #26930, the delete arm already reads the matched ids when a relation points at the object. ⛔ No second recompute routine: one rule, recomputeSummaries.
The first step is a red test on a real driver. Write a parent with a count roll-up and two children, delete or reparent both children by predicate, and read the parent.
Filing facts
Business reason: roll-up summaries are declared to stay correct on every child write. A bulk flow step that deletes or reassigns line items leaves the totals on the order wrong, with no error.
Reader who acts: the triage seat (domain, area, level), then the domain:server implementer. Clause-②: no is likely: no accept set changes, and a stored value becomes correct.
The card's order stands: a red test on a real driver first, then one rule, recomputeSummaries, fed the matched rows' old and new FKs. ⛔ No second recompute routine.
Claim: PM loop round 4 · card thread · lane refill under the shared cap of 4
Session: session_0186vfjHHMsRXC9EUT9hTFMo (the seat); the card thread is session_01FEkQ5mKGG79zhwLb1obtFs
Account: os-project-manager (the seat's linked user; the card's assignee)
Branch: claude/project-thread-q7o13v (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#2
Ruling-ref: triage 6455666642 (p1: a red test on a real driver first, then one rule, recomputeSummaries, fed the matched rows' old and new FKs; ⛔ no second recompute routine); seat ruling: the predicate arms of update() and delete() feed the existing recomputeSummaries, reading only the descriptors' FK columns and only when a summary names the object, ⛔ no new public option
File surface: packages/objectql/src/engine.ts (the predicate arms of update() and delete(), and a moved module only where the split series put a body they reach), the real-driver and flow-node tests (packages/objectql/**, packages/services/service-automation/**), and a changeset per package. ⛔ No packages/spec/**, ⛔ no new error code, ⛔ no second recompute routine, ⛔ no edit before #26930 has merged
Container & model: M, mode:thread, default model and effort (set at thread open)
Clause-②: no (likely: a stored roll-up becomes correct, no accepted value is refused; the dev re-measures and declares the arm)
Metadata-protocol: none
Responsibility: objectql predicate update/delete arms vs summary roll-up recompute (roll-up sibling of #26871)
Thread-read: 6455666642
Premise: holds (read 2026-10-27T23:04Z at origin/maine4ba846f: card open, unassigned, pm:queue with its triage line; no other seat has claimed it; the dev holds every edit until #26930 has merged); the dev re-measures it first, and a refutation is a premise_still_valid: false hand-back
Resume-from: none — fresh from origin/main
Serial constraints cleared: yes — the dev re-measures open PRs on the file surface before the first edit and before pr_create
State: pm:queue → pm:dispatched, assigned to os-project-manager, in this act.
os-dev-report
{
"issue": 26950,
"status": "done",
"branch": "claude/project-thread-q7o13v",
"pr": "#27000",
"session": "session_01FEkQ5mKGG79zhwLb1obtFs",
"premise_still_valid": true,
"summary": "The predicate arms of ObjectQL.update() and ObjectQL.delete() (multi: true, no scalar where.id) now hand the existing recomputeSummaries the matched rows' foreign keys: a delete the old parents, an update the old and the new (rows merged with the payload the driver receives), so a reparent recomputes both. Per the seat ruling: no second recompute routine, no new option, no refusal; an object no roll-up aggregates keeps its single updateMany / deleteMany and reads nothing new; otherwise only the descriptors' FK columns are read, once, before the write, through the write's own composed AST and driver options, and an existing matched-row read serves instead (update: the readPriorRows memo when a consumer made it; delete: #26930's walk id read now reads ['id', ...fk], or with no walk the per-row hook read). Measured while landing it: a self roll-up (a summary over its own object) predicate delete that removes a parent and its child threw SummaryRecomputeError after the delete (the parent update answers not-found), so withoutDeletedSelfParents (summary-aggregate.ts) clears, on the walk arm only, the self-referencing FK values naming a deleted row; it is input preparation for recomputeSummaries, not a second routine, and it is pinned and ablated. Recompute placement and failure surface are the by-id arm's (SummaryRecomputeError, no new code). File surface: the helper lives in packages/objectql/src/summary-aggregate.ts (the roll-up core module, beside SummaryDescriptor), which the claim's File surface line does not name; please amend the claim's surface if you accept it there, or say so and I move it into engine.ts.",
"tests": "Red first on base 40264be: service-automation src/builtin/predicate-write-summary-recompute.integration.test.ts (real ObjectKernel + ObjectQLPlugin + SqlDriver better-sqlite3 + AutomationServicePlugin) Tests 6 failed (6), e.g. 'expected { count: 2, total: 42 } to deeply equal { count: +0, total: +0 }'; objectql src/summary-rollup.test.ts with engine.ts restored to base blob fb30f0f7 and objectql rebuilt: Tests 4 failed | 18 passed (22), the no-roll-up control green; restore proved blob 31029717 == HEAD, git diff HEAD empty, rebuilt. Green: objectql full suite on f6f8939 Test Files 475 passed (475), Tests 8883 passed (8883); service-automation full suite on f6f8939 Test Files 215 passed (215), Tests 2752 passed (2752); both typechecks exit 0 (objectql re-run exit 0 after the final type-only cast commit); on final head e3a38f1 after a full build: summary-rollup + engine-cascade-delete-atomic + engine-nested-relation-lowering 55 passed, the new flow file + delete-record-predicate-referential-walk 8 passed; eslint --no-inline-config packages/objectql packages/services/service-automation exit 0. Ablations via scripts/ablation-replace.mjs (anchor 1->0, blob moved, restored to HEAD blob, git diff HEAD empty), objectql rebuilt, ablation-dist-preflight marker present in dist where the flow file read it: (1) update hands no new FKs: flow 2 failed, objectql 2 failed; (2) walk arm keeps no FK rows: flow 1 failed, objectql 1 failed; (3) no reuse of the per-row hook read: objectql 1 failed, flow file green as expected (an extra read changes no total; the build's DTS step failed on the unused binding but the ESM dist carried the marker); (4) self roll-up filter removed: objectql 1 failed with SummaryRecomputeError. Restore leg rebuilt objectql, preflight --absent clean for every marker.",
"gates": "Local, on e3a38f1 after a full turbo build: dispatch-gates --commands derived 65 families, each run with exit captured before any pipe: 65 exit 0; dispatch-gates --ran: 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN. First sweep on f6f8939: check:query-options-erasure exit 1 (engine.ts erasure 8 -> 10, the two new driver reads' option casts; fixed by typing them as IDataDriver['find'] third parameter, ratchet holds), check-plugin-teardown-shape --self-test exit 3 (pinned fixture commit fetched with git fetch --depth=1, then exit 0). CI families (the check:* farm, Test Core, Test UI and the rest of the required nine): in_progress at report time, see PR #27000 check-runs; not re-run locally.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "3 — mcp__github__pull_request_read (get #26930), mcp__github__issue_read (get #26950), mcp__github__issue_read (get_comments #26950); no write tool",
"api_writes": "2 — relay pr_create (PR #27000, via scripts/pm/fleet-write/dispatch.mjs, run 40610803882) and this os-dev-report comment on #26950 (scripts/pm/post-stamped.mjs); git push is not a REST write",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed: none found beyond the self roll-up false failure, which this PR fixes in place (same defect class, same file surface, same gate families) and pins."
]
}
PR #27000 merged at 2026-10-28T01:56:24Z. The merge queue landed it as 0eb2d21d05 on main (Fixes #26950; ACCEPT 6457968942; Clause-②: no). GitHub closed the card as completed.
Landed: a predicate update or delete (multi: true with a filter, as a flow's bulk update_record / delete_record sends) now recomputes the parent roll-up summaries its matched rows name. A delete recomputes the old parents. An update recomputes the old and the new ones, so a reparent fixes both totals.
Cost: an object that no roll-up aggregates reads nothing new. Otherwise the write reads the foreign-key columns once, before it writes, or reuses a read it already makes.
Residual (from the ACCEPT, not filed): a self roll-up whose relationshipField names a non-relation column could still meet SummaryRecomputeError on a predicate delete. That is outside the field's documented contract.
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage:
domain:server, p1,area:records· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T22:58Z⛔ Not a claim.
summaryroll-ups stale, silently. Order totals stay wrong after a bulk flow step, with no error. It is the roll-up sibling of objectql: a predicate delete (multi: true with a filter) skips the referential walk, so a flow's bulk delete_record bypasses restrict, master_detail cascade and set_null #26871 (p1, the predicate delete's referential walk), so the same grade.recomputeSummaries, fed the matched rows' old and new FKs. ⛔ No second recompute routine.findingremoved,pm:queueadded.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 4 · card thread · lane refill under the shared cap of 4
Session:
session_0186vfjHHMsRXC9EUT9hTFMo(the seat); the card thread issession_01FEkQ5mKGG79zhwLb1obtFsAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-q7o13v(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#2Ruling-ref: triage
6455666642(p1: a red test on a real driver first, then one rule,recomputeSummaries, fed the matched rows' old and new FKs; ⛔ no second recompute routine); seat ruling: the predicate arms ofupdate()anddelete()feed the existingrecomputeSummaries, reading only the descriptors' FK columns and only when a summary names the object, ⛔ no new public optionFile surface:
packages/objectql/src/engine.ts(the predicate arms ofupdate()anddelete(), and a moved module only where the split series put a body they reach), the real-driver and flow-node tests (packages/objectql/**,packages/services/service-automation/**), and a changeset per package. ⛔ Nopackages/spec/**, ⛔ no new error code, ⛔ no second recompute routine, ⛔ no edit before #26930 has mergedContainer & model:
M,mode:thread, default model and effort (set at thread open)Clause-②: no (likely: a stored roll-up becomes correct, no accepted value is refused; the dev re-measures and declares the arm)
Metadata-protocol: none
Responsibility:
objectqlpredicate update/delete arms vssummaryroll-up recompute (roll-up sibling of #26871)Thread-read: 6455666642
Premise: holds (read 2026-10-27T23:04Z at
origin/maine4ba846f: card open, unassigned,pm:queuewith its triage line; no other seat has claimed it; the dev holds every edit until #26930 has merged); the dev re-measures it first, and a refutation is apremise_still_valid: falsehand-backResume-from: none — fresh from
origin/mainSerial constraints cleared: yes — the dev re-measures open PRs on the file surface before the first edit and before
pr_createState:
pm:queue→pm:dispatched, assigned toos-project-manager, in this act.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
os-dev-report
{
"issue": 26950,
"status": "done",
"branch": "claude/project-thread-q7o13v",
"pr": "#27000",
"session": "session_01FEkQ5mKGG79zhwLb1obtFs",
"premise_still_valid": true,
"summary": "The predicate arms of ObjectQL.update() and ObjectQL.delete() (multi: true, no scalar where.id) now hand the existing recomputeSummaries the matched rows' foreign keys: a delete the old parents, an update the old and the new (rows merged with the payload the driver receives), so a reparent recomputes both. Per the seat ruling: no second recompute routine, no new option, no refusal; an object no roll-up aggregates keeps its single updateMany / deleteMany and reads nothing new; otherwise only the descriptors' FK columns are read, once, before the write, through the write's own composed AST and driver options, and an existing matched-row read serves instead (update: the readPriorRows memo when a consumer made it; delete: #26930's walk id read now reads ['id', ...fk], or with no walk the per-row hook read). Measured while landing it: a self roll-up (a summary over its own object) predicate delete that removes a parent and its child threw SummaryRecomputeError after the delete (the parent update answers not-found), so withoutDeletedSelfParents (summary-aggregate.ts) clears, on the walk arm only, the self-referencing FK values naming a deleted row; it is input preparation for recomputeSummaries, not a second routine, and it is pinned and ablated. Recompute placement and failure surface are the by-id arm's (SummaryRecomputeError, no new code). File surface: the helper lives in packages/objectql/src/summary-aggregate.ts (the roll-up core module, beside SummaryDescriptor), which the claim's File surface line does not name; please amend the claim's surface if you accept it there, or say so and I move it into engine.ts.",
"tests": "Red first on base 40264be: service-automation src/builtin/predicate-write-summary-recompute.integration.test.ts (real ObjectKernel + ObjectQLPlugin + SqlDriver better-sqlite3 + AutomationServicePlugin) Tests 6 failed (6), e.g. 'expected { count: 2, total: 42 } to deeply equal { count: +0, total: +0 }'; objectql src/summary-rollup.test.ts with engine.ts restored to base blob fb30f0f7 and objectql rebuilt: Tests 4 failed | 18 passed (22), the no-roll-up control green; restore proved blob 31029717 == HEAD, git diff HEAD empty, rebuilt. Green: objectql full suite on f6f8939 Test Files 475 passed (475), Tests 8883 passed (8883); service-automation full suite on f6f8939 Test Files 215 passed (215), Tests 2752 passed (2752); both typechecks exit 0 (objectql re-run exit 0 after the final type-only cast commit); on final head e3a38f1 after a full build: summary-rollup + engine-cascade-delete-atomic + engine-nested-relation-lowering 55 passed, the new flow file + delete-record-predicate-referential-walk 8 passed; eslint --no-inline-config packages/objectql packages/services/service-automation exit 0. Ablations via scripts/ablation-replace.mjs (anchor 1->0, blob moved, restored to HEAD blob, git diff HEAD empty), objectql rebuilt, ablation-dist-preflight marker present in dist where the flow file read it: (1) update hands no new FKs: flow 2 failed, objectql 2 failed; (2) walk arm keeps no FK rows: flow 1 failed, objectql 1 failed; (3) no reuse of the per-row hook read: objectql 1 failed, flow file green as expected (an extra read changes no total; the build's DTS step failed on the unused binding but the ESM dist carried the marker); (4) self roll-up filter removed: objectql 1 failed with SummaryRecomputeError. Restore leg rebuilt objectql, preflight --absent clean for every marker.",
"gates": "Local, on e3a38f1 after a full turbo build: dispatch-gates --commands derived 65 families, each run with exit captured before any pipe: 65 exit 0; dispatch-gates --ran: 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN. First sweep on f6f8939: check:query-options-erasure exit 1 (engine.ts erasure 8 -> 10, the two new driver reads' option casts; fixed by typing them as IDataDriver['find'] third parameter, ratchet holds), check-plugin-teardown-shape --self-test exit 3 (pinned fixture commit fetched with git fetch --depth=1, then exit 0). CI families (the check:* farm, Test Core, Test UI and the rest of the required nine): in_progress at report time, see PR #27000 check-runs; not re-run locally.",
"dogfood_battery": "n/a — no
!in the PR title and no (narrowing) arm","mcp_calls": "3 — mcp__github__pull_request_read (get #26930), mcp__github__issue_read (get #26950), mcp__github__issue_read (get_comments #26950); no write tool",
"api_writes": "2 — relay pr_create (PR #27000, via scripts/pm/fleet-write/dispatch.mjs, run 40610803882) and this os-dev-report comment on #26950 (scripts/pm/post-stamped.mjs); git push is not a REST write",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed: none found beyond the self roll-up false failure, which this PR fixes in place (same defect class, same file surface, same gate families) and pins."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Landed
2026-10-28T01:57Z ·
domain:serverseat 2 ·os-project-manager· sessionsession_0186vfjHHMsRXC9EUT9hTFMoPR #27000 merged at 2026-10-28T01:56:24Z. The merge queue landed it as
0eb2d21d05onmain(Fixes #26950; ACCEPT6457968942;Clause-②: no). GitHub closed the card as completed.git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-27000-*'git merge-base --is-ancestor 0eb2d21d05 origin/mainorigin/mainby contentpackages/objectql/src/summary-aggregate.tswithoutDeletedSelfParentsmulti: truewith a filter, as a flow's bulkupdate_record/delete_recordsends) now recomputes the parent roll-up summaries its matched rows name. A delete recomputes the old parents. An update recomputes the old and the new ones, so a reparent fixes both totals.relationshipFieldnames a non-relation column could still meetSummaryRecomputeErroron a predicate delete. That is outside the field's documented contract.Generated by Claude Code