Skip to content

DELETE_RESTRICTED (409) message is shown verbatim to end users: English-only, leaks API names, and contains developer-facing advice #7307

Description

@baozhoutao

Summary

When a delete is refused by referential integrity, the engine throws a 409 DELETE_RESTRICTED
whose message is surfaced verbatim to end users by Console's toast. That message is
English-only, embeds machine object/field names, and hands the user a developer instruction
(set deleteBehavior:'cascade' on ...) — none of which is actionable or comprehensible for a
business user of a localized app.

The error transport itself is fine (status is set, so the message is not swallowed into a
generic 500). This is purely about the user-facing copy.

Where

packages/objectqlcascadeDeleteRelations, the behavior === "restrict" branch
(observed in @objectstack/objectql@17.0.0-rc.5, dist/core.js):

const reason = fdef.deleteBehavior !== "restrict" && fdef.required === true
  ? ` (${fieldName} is required, so it cannot be cleared)` : "";
const err = new Error(
  `Cannot delete ${object} (${id}): ${dependents.length} dependent ${childName} record(s) ` +
  `reference it via ${fieldName}${reason}. Delete or reassign them first, ` +
  `or set deleteBehavior:'cascade' on ${childName}.${fieldName}.`
);
err.code = "DELETE_RESTRICTED";
err.status = 409;
err.object = object;
err.dependentObject = childName;
err.dependentCount = dependents.length;

Actual behaviour

Deleting a sys_business_unit referenced by a required lookup produces this red toast in
Console
, shown as-is to the operator in an otherwise fully Chinese UI:

Cannot delete sys_business_unit (<id>): 1 dependent os_tianshun_ehr_sporadic_application
record(s) reference it via apply_dept (apply_dept is required, so it cannot be cleared).
Delete or reassign them first, or set deleteBehavior:'cascade' on
os_tianshun_ehr_sporadic_application.apply_dept.

Three separate problems for the end user:

  1. Not localized — English string in a zh-CN app; there is no i18n key to override.
  2. Machine names leakos_tianshun_ehr_sporadic_application, apply_dept. The user
    knows these as 「零星申请」and「申报部门」; the raw API names mean nothing to them.
  3. Developer advice shown to end usersset deleteBehavior:'cascade' on ... is a
    metadata-authoring instruction. A business user cannot act on it, and it invites
    support tickets asking where that setting lives.

Expected

A business user should get something like:「该 <对象label> 正被 1 条 <子对象label> 通过
「<字段label>」引用,且该字段为必填,无法清空。请先删除或改派这些记录。」 — i.e. localized,
using labels rather than API names, and without the metadata-authoring hint.

The structured fields (code, object, dependentObject, dependentCount) are already
present and are the right channel for developers/logs — the developer guidance belongs there
(or in a separate detail/devHint property), not in the user-visible message.

Suggested direction

  • Emit message through the i18n layer with an overridable key (e.g.
    errors.deleteRestricted), interpolating resolved labels for object/field where
    available and falling back to API names only when no label exists;
  • Move Delete or reassign them first, or set deleteBehavior:'cascade' on ... out of
    message into a developer-only field (or drop the deleteBehavior half of it entirely —
    the "delete or reassign them first" half is useful to end users);
  • Keep status: 409 and the structured fields exactly as they are — that part works well.

Repro

  1. Object A with a lookup field on object B where required: true (so set_null is coerced to restrict);
  2. Create one B record and one A record referencing it;
  3. Delete the B record from Console (record page, drawer, list row action, or bulk bar — all four surface the same toast);
  4. Observe the raw English message in the toast.

Found while building an app on 17.0.0-rc.5; reported per "platform issues go to the platform repo".

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions