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
claimSeedOwnership claims nothing at all on an object with more than 10k rows under one unowned predicate, because MAX_BULK_PER_ROW_HOOK_ROWS refuses the whole write #14719
Follow-up recorded while implementing #14530 (PR #14718). Not fixed there: #14530's ruled scope is disposition 2 (turn the single-id loop into a predicate write), and paginating the claim is a different decision. Filing it so the boundary is on the record rather than only in a PR body.
The shape
claimSeedOwnership (packages/plugins/plugin-security/src/claim-seed-ownership.ts) now issues one predicate write per unowned shape:
A predicate write carries no limit, so the bound is now the engine's own per-row hook ceiling. MAX_BULK_PER_ROW_HOOK_ROWS is 10 000 (packages/spec/src/data/bulk-write-hook-conformance.ts), and D6 makes the refusal total — a predicate write matching more than that on an object carrying beforeUpdate/afterUpdate hooks is refused whole, never downgraded. Every object carries them in practice: objectql's own audit-stamp and fetch-previous builtins are registered on object: '*'.
Measured on a real ObjectQL engine (in-memory driver, 21 000 seeded rows, 10 500 per predicate):
WARN [security] claimSeedOwnership failed for crm_lead; those rows stay unowned and the next run will claim them
Refusing the bulk write on 'crm_lead': it matches 10500 rows, and 'beforeUpdate' hooks are contracted to fire
PER ROW on a predicate write (ADR-0058, bulk-write addendum), which is over the 10000-row ceiling for one write.
Nothing was written. Narrow the predicate so the batch matches fewer rows (paginate the write), or remove the
'beforeUpdate' hook from this object.
claimed: 0.
What it is and is not
Not a regression in reachable population. The pre-#14530 code scanned at limit: 10_000 per predicate, so it too could only ever claim 10 000 rows per predicate per run.
It is a change in what happens past that line. The old code claimed the first 10 000 rows silently and left the rest; the new code claims none of them and logs the engine's refusal. The old half-claimed state is also not self-healing: bootstrapPlatformAdmin short-circuits at already_have_admin (bootstrap-platform-admin.ts:412) before the claim on every later pass, so nothing re-runs the claim automatically once an admin exists. Loud-and-total was judged better than silent-and-partial for #14530's purpose, but neither one finishes the job for a large seeded dataset.
Why it is left open rather than solved
The obvious fix — page the claim — is the shape #14530 deliberately deleted, so it needs a decision rather than a reflex. Sketch of the options, unranked:
Leave it. An app seeding more than 10 000 rows of one object with no owner_id is outside anything shipped; the operator now gets an actionable message naming the remedy.
Raise or exempt the ceiling for this path. Almost certainly wrong — the ceiling is a per-row hook fan-out bound the sharing hooks are exactly the reason for, and exempting a system writer from it is the shape 审批回写(系统身份)不触发共享规则物化,「批准后团队看不见」——平台只记一条日志、无补偿、无声明式手段 #13533 just closed on the other side.
Wants a maintainer or triage call, not an implementer's.
Where to look
packages/plugins/plugin-security/src/claim-seed-ownership.ts — the two predicate writes and the per-predicate warn.
Follow-up recorded while implementing #14530 (PR #14718). Not fixed there: #14530's ruled scope is disposition 2 (turn the single-id loop into a predicate write), and paginating the claim is a different decision. Filing it so the boundary is on the record rather than only in a PR body.
The shape
claimSeedOwnership(packages/plugins/plugin-security/src/claim-seed-ownership.ts) now issues one predicate write per unowned shape:A predicate write carries no
limit, so the bound is now the engine's own per-row hook ceiling.MAX_BULK_PER_ROW_HOOK_ROWSis 10 000 (packages/spec/src/data/bulk-write-hook-conformance.ts), and D6 makes the refusal total — a predicate write matching more than that on an object carryingbeforeUpdate/afterUpdatehooks is refused whole, never downgraded. Every object carries them in practice: objectql's own audit-stamp and fetch-previous builtins are registered onobject: '*'.Measured on a real ObjectQL engine (in-memory driver, 21 000 seeded rows, 10 500 per predicate):
claimed: 0.What it is and is not
Not a regression in reachable population. The pre-#14530 code scanned at
limit: 10_000per predicate, so it too could only ever claim 10 000 rows per predicate per run.It is a change in what happens past that line. The old code claimed the first 10 000 rows silently and left the rest; the new code claims none of them and logs the engine's refusal. The old half-claimed state is also not self-healing:
bootstrapPlatformAdminshort-circuits atalready_have_admin(bootstrap-platform-admin.ts:412) before the claim on every later pass, so nothing re-runs the claim automatically once an admin exists. Loud-and-total was judged better than silent-and-partial for #14530's purpose, but neither one finishes the job for a large seeded dataset.Why it is left open rather than solved
The obvious fix — page the claim — is the shape #14530 deliberately deleted, so it needs a decision rather than a reflex. Sketch of the options, unranked:
owner_idis outside anything shipped; the operator now gets an actionable message naming the remedy.Wants a maintainer or triage call, not an implementer's.
Where to look
packages/plugins/plugin-security/src/claim-seed-ownership.ts— the two predicate writes and the per-predicate warn.packages/spec/src/data/bulk-write-hook-conformance.ts—MAX_BULK_PER_ROW_HOOK_ROWS, D6,resolveBulkPerRowHookBudget.packages/objectql/src/engine.ts—assertBulkPerRowHookBudget, raised before the driver call so nothing is written.