Skip to content

feat(objectql)!: retire delete()'s by-id beforeDelete REPOINT, aligning with update() (#6752) - #7482

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-6752-retire-delete-byid-repoint
Aug 11, 2026
Merged

feat(objectql)!: retire delete()'s by-id beforeDelete REPOINT, aligning with update() (#6752)#7482
hotlong merged 1 commit into
mainfrom
claude/issue-6752-retire-delete-byid-repoint

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

⛔ MAINTAINER-MERGED ONLY — do not queue, do not arm auto-merge

This diff touches docs/adr/** (ADR-0058 gains Amendment II.2), so Prime
Directive #14
applies: ⛔ no AI seat merges it, adds it to the merge queue, or
calls enable_pr_auto_merge. Reviewed + approved + fully green does not
override this — an accepted ADR is the decision, so merging one is the act of
adopting a governance position. Left as a draft on purpose; the PM flips it
ready after verification, and the maintainer merges by hand.

Closes #6752.

What this is

Route 2 of the 2026-08-09 maintainer ruling on #6752: a beforeDelete
handler on a by-id delete() may no longer move the delete onto another row
by assigning ctx.input.id. The rebind is refused with HookTargetRebindError /
ERR_HOOK_TARGET_REBIND, path: 'by-id' — exactly what the update() twin
(ADR-0058 Amendment II.1, PR #6697) and both per-row paths (D4) already raised.
Nothing is deleted; afterDelete and the roll-up recompute never run.

The rule across both verbs is now one line: a by-id target is immutable in a
before* handler.

CLEARED id REBOUND to another id
update() by-id refused refused
delete() by-id refused refused (was honoured)
either, per-row refused (D4) refused (D4)

Writing the same id back stays legal — the check is input.id !== id, the
update() check verbatim.

⛔ Route 3 (building update() the same re-resolution) stays excluded by #5574's
recorded ruling; this PR does not reopen it.

This removes a capability that WORKED

Worth stating plainly, because the diff reads like a bug fix and is not one.
#5272's re-resolution was internally correct: it re-read the new target's
pre-image and rebound previous, so afterDelete and the summary recompute saw
the row actually deleted and nothing stale ever leaked. The case that retires a
rebind on update() — the write landing on a row whose pre-image, readonlyWhen
locks and validation rules were never evaluated — genuinely did not apply to it.
That is why PR #6697 left the asymmetry standing rather than folding a behaviour
removal into an ordering change.

The ruling retires it anyway, on three measured axes: compatibility cost zero,
one rule beats two individually-correct rules an author must memorize, and "a
hook silently redirects which row gets deleted" is a top-grade footgun for
authored — especially AI-authored — handlers however correctly the redirect is
implemented.

The compatibility grep, re-run on this branch's base (not inherited)

grep -rnE "(input|ctx\.input|rowCtx\.input)[^;]{0,20}\)?\.id\s*=[^=]" \
  --include=*.ts --include=*.tsx --include=*.md --include=*.mdx .

Six sites, all of them tests in packages/objectql/src/. No consumer anywhere
repoints
— not in the framework, plugins, examples or docs.

site what it is
bulk-write-per-row-hooks.test.ts:675 per-row rebind refusal pin (control)
bulk-write-per-row-hooks.test.ts:695 update() by-id CLEARED refusal pin (control)
bulk-write-per-row-hooks.test.ts:715 delete() by-id CLEARED refusal pin (control)
bulk-write-per-row-hooks.test.ts:746 the repoint pin flipped by this PR
bulk-write-per-row-hooks.test.ts:767 same-id negative control (control)
engine.test.ts:1107 clears the id to force the #2982 fail-closed assertion (control)

⚠️ One correction to the card: its grep was input\.id\s*= with input.id ===
excluded, which misses the (ctx.input as any).id = cast form every pin in
bulk-write-per-row-hooks.test.ts uses — the card reported one hit where there
are six. The conclusion is unchanged and now rests on a grep that can see the
pins
: every additional hit is a test of this very family, so the measured
consumer count is still zero.

The four landing points, all moved together

  1. ADR-0058 Amendment II.1 scope table — the delete()-by-id REBOUND cell
    moves from "honoured (单记录 delete 从不绑定 hookContext.previous —— 契约声明「for update/delete」,引擎只在 update 分支赋值;#5038 之后批量 delete 反而比单记录 delete 更完整 #5272's re-read, unchanged)" to refused, and the
    carve-out paragraph now points forward instead of deferring. New Amendment
    II.2
    records the ruling, its three axes, the replacement routes, and that
    route 3 stays excluded.
  2. hook-target-rebind-errors.ts's NOT-covered section — deleted, because
    there is no longer an exception to remember. Replaced by the full four-cell
    coverage table plus the record of why the last cell arrived late (the
    mechanism it removed was not broken).
    Not on the card, found by reading: the REBOUND branch of the error
    message was written for update() only — it claimed honouring a rebind "would
    write a row that none of those checks ever saw", which was never true on the
    delete path. Left alone it would have shipped a false explanation to the first
    author who hit the new refusal. Restated for both verbs.
  3. engine.ts's re-read blockmeasured dead, so removed, not just
    re-commented.
    Its guard was wantsPreImage && input.id !== id && input.id,
    which is precisely the case the refusal now throws on, so it is unreachable
    the moment the refusal lands. The single pre-dispatch pre-image read that
    binds previous for beforeDelete (单 id update 把同一行前置状态读了 3 次(engine 前置行门 + sys_fetch_previous_update + plugin-audit captureBefore),且后两次不受任何按对象需求门约束 #5846 (a) / feat(objectql): dispatch before* hooks per matched row on a predicate bulk write (#5574, #5846) #6697) is a different read
    and is untouched — readPreImage / bindPreImage survive with one call site
    each.
  4. The D4 §7 pin (bulk-write-per-row-hooks.test.ts) — flipped, never
    deleted.

#5272's pins, each disposition named

pin disposition
bulk-write-per-row-hooks.test.ts §7 still HONOURS a by-id beforeDelete REPOINT FLIPPED to REFUSES a by-id beforeDelete REPOINT — the last cell, retired by #6752. Same handler, same fixtures; now asserts the full envelope (code, name, object, event, path, expectedId, observedId, message), that neither row was deleted, and that afterDelete never ran. Its comment records the flip as a ruling, not a fix, and warns against "restoring symmetry" via route 3.
(new) leaves a by-id beforeDelete that rewrites the SAME id alone ADDED — the negative control the input.id !== id check rests on. Same-id rewrite still deletes the addressed row and afterDelete still describes it. Measured legal before this PR and after.
hook-condition-previous-scope.test.ts §[#5272] a single-record delete binds previous UNTOUCHED — pins previous binding, not the repoint. Passes unchanged.
engine-delete-prior-read-scope.test.ts (per-object read gate, #5272/#6697) UNTOUCHED — the surviving pre-dispatch read is unchanged. Passes unchanged.
plugin-audit/audit-bound-previous.test.ts:300 single-id delete() pays exactly ONE findOne UNTOUCHED, and now strictly easier to hold — the second read is gone. Passes unchanged.
engine.test.ts:1107 (#2982 fail-closed CLEARED) UNTOUCHED (control).
update() twin + CLEARED column + per-row D4 UNTOUCHED (controls), per the card.

⚠️ packages/spec IS touched — declaring it loudly

Three files, all ADR-0087 ledger machinery, no schema and no export surface
changed
(gen:api-surface reports zero drift):

  • packages/spec/src/migrations/entries/semantic/17.delete-by-id-before-hook-repoint-retired.ts — new entry
  • packages/spec/src/migrations/registry.ts — generated region, gen:migration-registry
  • packages/spec/spec-changes.json — generated, gen:spec-changes
  • (plus docs/protocol-upgrade-guide.md, generated by gen:upgrade-guide)

Flagging for the #6017 declaration the PM files.

Changeset + gate reading

.changeset/retire-delete-by-id-before-hook-repoint.md, @objectstack/objectql: major +
@objectstack/spec: patch, carrying <!-- adr-0087: registered delete-by-id-before-hook-repoint-retired -->.

⚠️ Correction to the dispatch brief on check-changeset-no-major.mjs. The
brief warned the fixed group ships breaking as minor. Read against the gate:
that rule is currently suspended — Changesets is in RC pre-release mode
(tag: rc), where a major only ever produces X.0.0-rc.N, so the guard stands
aside by design and says so. major is therefore both correct and green here,
matching the immediate precedent (708431313, registerhook-empty-target-refusal).
If the RC window exits before this lands, the bump must drop to minor.

The ADR-0087 disposition is registered rather than not-required because the
other three are mechanically false: @objectstack/objectql is published (not
unpublished), no prior id covers this surface (not already-registered), and
the changeset ships rewrite instructions, so no-migration-prescription is a
self-contradiction the gate refuses. The entry is semantic (D3), not a D2
conversion
, for the reason hook-context-session-roles-retired and
hook-register-empty-object-target-refused are at the same step: a HookContext
is never persisted, so there is no sys_metadata row to rewrite, and the intent
behind a repoint is not recoverable — only the author knows whether it meant
"delete that row instead" or "delete that row too".

Gates run locally — all completed: success

gate result
@objectstack/objectql suite 178 files / 3149 tests passed
@objectstack/spec suite 373 files / 9789 tests passed
@objectstack/plugin-audit suite 12 files / 201 tests passed
@objectstack/objectql typecheck ✅ clean (tsc --noEmit)
check:adr-anchors ✅ 47 anchored files, 21996 citations resolve
check:durability-log-level ✅ 24 seams, 63 read seams
check:engine-double-contract ✅ 142 pinned, 133 debt, 2 exempt
check:stack-collection-maps ✅ 7 enumerations reconciled
check-engine-split-ratio.mjs ✅ reports (ADR-0076 D7 metric, informational)
check-changeset-no-major.mjs ✅ RC pre-mode — guard stands aside, major noted
check-adr-0087-registration.mjs ✅ 1 declared-breaking changeset, disposition carried
check-empty-changeset.mjs ✅ not empty
spec check:migration-registry ✅ current (60 semantic / 16 retired-key / 45 retired-def)
spec check:spec-changes ✅ up to date
spec check:upgrade-guide ✅ up to date
spec gen:api-surface ✅ zero drift

⚠️ First run of plugin-audit showed 7 failed suites — traced to a stale
@objectstack/objectql build in the worktree (Failed to resolve entry for package), not to this diff. Green after rebuilding. Recorded so nobody re-derives it.

One thing left deliberately alone

The pending .changeset/bulk-write-before-hooks-per-row.md (PR #6697's) says "a
beforeDelete handler that repoints the target is unaffected" — true of that
PR, false of the release both ship in. I did not edit another card's pending
changeset: it is their artifact and a needless conflict magnet. My changeset
states the supersession instead. Flagging it so the release compiler can reconcile
the two entries.

Refs #6752, #5272, #5574, PR #6697, ADR-0058, ADR-0087.


Generated by Claude Code

…ng with update() (#6752)

A `beforeDelete` handler on a by-id `delete()` may no longer move the delete
onto another row by assigning `ctx.input.id`. It is refused with
`HookTargetRebindError` / `ERR_HOOK_TARGET_REBIND` (`path: 'by-id'`) — the
same refusal the `update()` twin and both per-row paths (D4) already raised.
Nothing is deleted; `afterDelete` and the roll-up recompute never run. The
rule across both verbs is now one line: a by-id target is immutable in a
`before*` handler.

This removes a capability that WORKED. #5272 RE-RESOLVED a repointed target,
re-reading its pre-image and rebinding `previous`, so nothing stale ever
reached a consumer — which is why #5574's engine half (PR #6697) deliberately
left the asymmetry standing rather than folding a behaviour removal into an
ordering change, and filed it as #6752. The 2026-08-09 maintainer ruling on
that card retires it on three measured axes: compatibility cost zero (the
repo-wide grep for assignments into a hook's `input.id`, re-run on this
branch's base, finds six sites and all six are this family's own pins), one
rule beats two individually-correct rules an author must memorize, and "a
hook silently redirects which row gets deleted" is a top-grade footgun for
authored handlers however correctly the redirect is implemented. Aligning the
other way — building `update()` the same re-resolution — stays excluded by
#5574's recorded ruling.

The four recorded landing points move together:

  * ADR-0058 Amendment II.1's scope table now shows the REBOUND column
    uniform, with the carve-out pointing forward to the new Amendment II.2,
    which records the ruling and its three axes.
  * `hook-target-rebind-errors.ts`'s "what this error does NOT cover" section
    is gone — there is no exception left to remember — replaced by the full
    four-cell coverage table and the record of why the last cell arrived late.
    The REBOUND message text was written for `update()` only and would have
    misdescribed the delete path; it now states the rule for both verbs.
  * `engine.ts`'s re-read block is DELETED, not merely bypassed: its guard was
    `input.id !== id && input.id`, exactly the case the refusal now throws on,
    so it became unreachable code. The single pre-dispatch pre-image read that
    binds `previous` for `beforeDelete` is a different read and is untouched.
  * The D4 §7 pin is FLIPPED to assert the refusal with its full envelope
    (code, name, object, event, path, expectedId, observedId, message), plus
    that neither row was deleted and `afterDelete` never ran. Flipped, not
    deleted — the case is the record of a ruling. A new negative control pins
    that a same-id rewrite stays legal, which is the measurement the
    `input.id !== id` check rests on.

Registers the ADR-0087 step-17 semantic entry
`delete-by-id-before-hook-repoint-retired` (a runtime hook surface: no
`sys_metadata` row for the D2 chain to rewrite, and the intent behind a
repoint is not recoverable, so it is a D3 semantic TODO). Artifacts
regenerated (`gen:migration-registry`, `gen:spec-changes`,
`gen:upgrade-guide`).

Refs #6752, #5272, #5574, PR #6697, ADR-0058, ADR-0087

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CxyoeX26ZjnnwzbegYvTHW
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 10, 2026 6:47pm

Request Review

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Aug 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/objectql, @objectstack/spec.

109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/objectql, packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/permissions/system-context.mdx (via packages/objectql, packages/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

7 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@hotlong
hotlong added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit 8dd98bf Aug 11, 2026
28 of 29 checks passed
@hotlong
hotlong deleted the claude/issue-6752-retire-delete-byid-repoint branch August 11, 2026 02:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

delete() by-id 仍允许 beforeDelete 重定向 input.id —— 与 update() 侧的 ADR-0058 Amendment II.1 不对称,需定夺是否一并退休

3 participants