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
Hook and action reads across the app pass their predicate as filter:. The
ObjectQL kernel only honours where: on findOne and count — filter is
dropped without an error and without a log line. find happens to alias it,
which is why the spelling looked legitimate and spread by copy-paste.
findOne({ filter: { id } }) → the object's first row (the engine also
applies limit: 1), not the requested record and not null.
count({ filter: {...} }) → counts the whole object.
Writes were never affected: every write targets an explicit id. The damage is
entirely on the read side — guards and lookups evaluated against an unrelated
row, and values computed from it.
Impact observed (17 call sites, 8 files)
Money is wrong.opportunity_line_item / quote_line_item fill list_price / unit_price from the first product in the catalogue instead of
the selected one, and that value rolls up into deal amounts and quote totals.
After fix(line-items): F/P expression tags, null guard, shared price-fill (#514 items 8, 3, 15) #570 extracted the shared _line-item-price-fill.ts, the same defect
applies to both line-item objects from one place.
Delete guards on crm_product / crm_account count the entire object,
blocking deletes with no real references and reporting a fabricated count.
Campaign ROI snapshots record whole-object opportunity/lead counts as
per-campaign attribution.
Stage/status gates (quote → opportunity stage, case → account owner, contract / opportunity rollup-freeze guards) read an unrelated row; the
subsequent write then correctly targets the intended id, so the guard
protected the wrong record.
Why the test suite stayed green
Three layers each failed to catch it:
src/objects/_hook-api.ts hand-rolls the query type with both filter? and where? optional, and its comment stated that drivers accept
either — so TypeScript green-lit the wrong key.
test/helpers/hook-harness.ts resolved its predicate as q.filter ?? q.where ?? {} — a stand-in more permissive than the real
kernel turns the whole runtime-hook suite into a green light for broken
code.
Nothing logs anything at runtime; there is no string to grep for.
Fix
PR #573 — remove filter? from the hand-rolled type so the compiler surfaces
every site (that is what raised the count from the 4 originally suspected to
17), correct the call sites, make the harness throw on filter, and add a
real-kernel characterisation test plus a source scan over all of src/objects/
(not just *.hook.ts — the shared price-fill module is not a .hook.ts file
and an earlier, narrower scan missed exactly the most expensive site).
Hook and action reads across the app pass their predicate as
filter:. TheObjectQL kernel only honours
where:onfindOneandcount—filterisdropped without an error and without a log line.
findhappens to alias it,which is why the spelling looked legitimate and spread by copy-paste.
Kernel-side report: objectstack-ai/objectstack#4419
What actually happens
findOne({ filter: { id } })→ the object's first row (the engine alsoapplies
limit: 1), not the requested record and notnull.count({ filter: {...} })→ counts the whole object.Writes were never affected: every write targets an explicit id. The damage is
entirely on the read side — guards and lookups evaluated against an unrelated
row, and values computed from it.
Impact observed (17 call sites, 8 files)
opportunity_line_item/quote_line_itemfilllist_price/unit_pricefrom the first product in the catalogue instead ofthe selected one, and that value rolls up into deal amounts and quote totals.
After fix(line-items): F/P expression tags, null guard, shared price-fill (#514 items 8, 3, 15) #570 extracted the shared
_line-item-price-fill.ts, the same defectapplies to both line-item objects from one place.
crm_product/crm_accountcount the entire object,blocking deletes with no real references and reporting a fabricated count.
per-campaign attribution.
quote→ opportunity stage,case→ account owner,contract/opportunityrollup-freeze guards) read an unrelated row; thesubsequent write then correctly targets the intended id, so the guard
protected the wrong record.
Why the test suite stayed green
Three layers each failed to catch it:
src/objects/_hook-api.tshand-rolls the query type with bothfilter?andwhere?optional, and its comment stated that drivers accepteither — so TypeScript green-lit the wrong key.
test/helpers/hook-harness.tsresolved its predicate asq.filter ?? q.where ?? {}— a stand-in more permissive than the realkernel turns the whole runtime-hook suite into a green light for broken
code.
Fix
PR #573 — remove
filter?from the hand-rolled type so the compiler surfacesevery site (that is what raised the count from the 4 originally suspected to
17), correct the call sites, make the harness throw on
filter, and add areal-kernel characterisation test plus a source scan over all of
src/objects/(not just
*.hook.ts— the shared price-fill module is not a.hook.tsfileand an earlier, narrower scan missed exactly the most expensive site).