Skip to content

DELETE is broken for any object targeted by a multiple: true reference — 400 INVALID_FILTER and the row survives (regression since #8895) #9390

Description

@os-zhuang

Found by the priority:P0 round of the #9296 QA wave; run record #9334. Subject sha e4e5c6e3c608b1b807c83a0d5b734f213eb1a1dd, stock showcase.

Symptom

DELETE /api/v1/data/showcase_account/<id>   →  400 INVALID_FILTER

The row survives. This is not a refusal-with-a-reason (the platform has one of those — see the contrast below); it is the delete path failing and reporting a filter error the caller never wrote.

Cause

cascadeDeleteRelations (packages/objectql/src/engine.ts, ~10112) probes for dependents before deleting, issuing a bare-equality query per referencing field:

where: { [fieldName]: id }

showcase_field_zoo.f_lookups is a multi-value lookup at showcase_account — Field.lookup('showcase_account', { label: 'Lookup → Accounts (multiple)', multiple: true }) (examples/app-showcase/src/data/objects/field-zoo.object.ts:106), stored as JSON TEXT. Bare equality against a JSON-TEXT multi-value column is exactly what the engine's own #5869 guard refuses — so the dependent probe throws INVALID_FILTER, and the #8895 catch, which only swallows missing-table errors, rethrows it and aborts the whole delete.

Why it is a regression, not a latent bug: before #8895 that catch swallowed the failure, so the delete completed. #8895 tightened the catch for good reasons (it had been inventing "no dependents" for a relation whose probe could not run) and this case fell out of the tightening.

Blast radius — measured, not inferred

On stock showcase only showcase_account is affected: it is the sole object targeted by a multiple: true reference. Verified contrasts on the same boot:

object DELETE result
showcase_account 400 INVALID_FILTER, row survives ← the defect
team / category / announcement / private_note / project 200, deleted
product 409 DELETE_RESTRICTED — a correct guard, not this bug
sys_user 405 — escapes via method, not via this path

Generally: any object that is the target of a multiple: true reference field cannot be deleted. Stock showcase has one; a customer schema can have many, and the failure is silent-looking — a 400 about a filter gives no hint that a multi-value lookup elsewhere is the cause.

Fix direction (not prescribed — the owning lane decides)

The dependent probe needs to be $contains-aware: when the referencing field is multiple: true, probe with the containment operator the engine already supports for JSON-TEXT multi-value columns instead of bare equality. Landing in packages/objectql; the console is not involved.

Whatever the shape, the regression test should assert both halves — the delete succeeds, and a genuine dependent through a multi-value reference is still detected (so the fix does not restore the old bug of inventing "no dependents").

Reproduction

pnpm -C examples/app-showcase exec objectstack dev --ui --seed-admin -p <port> -d file:/tmp/<run>/del.db
# authenticate as the seeded admin, then:
curl -X DELETE http://localhost:<port>/api/v1/data/showcase_account/<any seeded account id>
  • expected: 200, row gone.
  • actual: 400 INVALID_FILTER, row still present on re-read.

Left unlabelled for domain:* — routing is the triage seat's call (the fix lands in packages/objectql). Levelling likewise: this is a P0 checklist-item failure and a data-integrity regression on a published surface, but the priority label is triage's to set.

Activity

  1. os-zhuang commented on Aug 18, 2026

    @os-zhuang
    ContributorAuthor

    Cross-reference for triage — this is the same defect as #9362 (extracted from run record #9351), which was dispatched and implemented in draft PR #9437. No keyword here is meant to close this card; leaving the dedup call to triage.

    Two things from the implementation worth carrying back to this report:

    The guard referenced above is #7398 (the JSON-column operator gate in driver-sql), not #5869.


    Generated by Claude Code


    Generated by Claude Code

  2. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    Triage: lands in packages/objectql (cascadeDeleteRelations' dependent probe must be $contains-aware for multiple: true referencing fields) ⇒ domain:engine-core; queued as Bug — a measured data-integrity regression on a published surface (DELETE 400s and the row survives for any object targeted by a multi-value reference). priority:p0 and target:v17 set: fails the binary release test on class ① (a stock-showcase user hits it today, and any customer schema with a multiple: true lookup is affected). The regression test must pin both halves as the card states — delete succeeds AND a genuine multi-value dependent is still detected, so the fix does not reopen #8895's invented-"no dependents" bug. Family note for the dispatching seat: #9285 (same file family, queued this round) touches plugin.ts while this touches engine.ts cascadeDeleteRelations — different files, but both sit in packages/objectql, so same-package batch-independence applies. (Triage seat, session session_019gCKd9EZfQ6MbGTnMHHJvW.)


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions