Skip to content

docs(spec,objectql): declare that after* hooks fire inside the unit of work (#7477) - #7502

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-7477-afterdelete-in-transaction-semantics
Aug 11, 2026
Merged

docs(spec,objectql): declare that after* hooks fire inside the unit of work (#7477)#7502
os-zhuang merged 1 commit into
mainfrom
claude/issue-7477-afterdelete-in-transaction-semantics

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #7477

Implements the maintainer ruling recorded on the card 2026-08-11 02:54Z, quoted verbatim and untranslated:

Ruling: Option 1 — after* hooks are declared to fire inside the unit of work ("the write has been requested and will happen unless this unit is undone"); a hook with external side effects is responsible for tolerating rollback. The work: document this semantics on the hook API surface (JSDoc + docs page), citing this card.

Zero behaviour change. No executable code and no existing test assertion was modified. The only test-file edit is a comment (see below).

Premise, verified against origin/main @ 211abdb

Re-derived by content, not by the line anchors in the issue body (engine.ts took #7482 / 8dd98bf the same day, so those had shifted):

  • packages/objectql/src/engine.ts — the by-id delete path builds runByIdDelete, which calls cascadeDeleteRelations and then the parent's own driver.delete, and runs that whole closure inside this.transaction(...) when planCascadeAtomicity(object) === 'atomic'. The parent's hookContext.event = 'afterDelete' / triggerHooks('afterDelete', …) sits after that wrap closes; each cascaded child re-enters this.delete() inside it, so the children's afterDelete fires inside a rollback-able unit. Confirmed.
  • packages/metadata-protocol/src/protocol.tsrunAtomicBatch runs the whole per-record loop inside one engine.transaction(...) and aborts it via the ABORT sentinel when outcome.failed > 0, so every member's after* has already fired when the rollback happens. Confirmed.

Both halves of the premise hold; the card is implementable as ruled.

Where the statement landed — measured, not assumed

The PM's mechanism assumption was that authors read hook semantics in the engine's hook-dispatch region. Measurement moved the primary surface: packages/objectql/src/types.ts and hook-wrappers.ts have no afterDelete at all, and the authorable hook surface is packages/spec/src/data/hook.zod.tsHookEvent is what HookSchema.events is typed by, and it already carries the repo's existing per-event semantics prose (the #3195 read/write blocks). So the statement is written at that producer, plus the engine-side surfaces a code-registered hook is written against.

File Surface What was added
packages/spec/src/data/hook.zod.ts HookEvent (authorable enum) JSDoc block: the declared meaning, the three paths that open a unit of work, in-engine vs external effects
packages/spec/src/data/hook.zod.ts HookEventType Short JSDoc pointing at HookEvent
packages/objectql/src/engine.ts DISPATCHABLE_HOOK_EVENTS The full engine-side statement, incl. why deferring to commit was rejected, and the parent-vs-cascaded-child distinction
packages/objectql/src/engine.ts HookHandler JSDoc on the signature a code-registered hook is written against
packages/objectql/src/engine.ts triggerHooks Note that dispatch does not wait for a commit, and that deferring one would change the ruling
content/docs/automation/hooks.mdx Hooks docs page New section "After hooks run inside the unit of work" + a pointer from "After Hook" + one DON'T bullet

Extra files beyond the PM's expected surface, and files deliberately not touched:

  • Added: packages/spec/src/data/hook.zod.ts (the real producer — see above).
  • Not touched: packages/objectql/src/lifecycle/lifecycle-service.ts and src/plugin.ts. Both merely mention afterDelete (the reaper's per-row dispatch note; a retired builtin's comment); neither is a surface an author reads hook semantics from, so documenting there would be a consumer-side copy of one statement.
  • Not touched: content/docs/references/data/hook.mdx is AUTO-GENERATED from the spec file. Its generator renders Zod .describe() text, not JSDoc, so the new JSDoc does not reach it and check:generated stays green with no regeneration. Putting the statement in a .describe() instead would have pushed it into the authorable JSON-schema artifacts and the Studio form tooltip — the wrong length and the wrong audience for a form hint — so the teaching text lives on the hand-written page, which is where hooks are actually taught. Flagging the choice rather than burying it.

The one test-file edit

packages/objectql/src/engine-cascade-delete-atomic.test.ts already pins exactly the ordering being declared — a refused cascade whose child afterDelete has fired, with the parent's suppressed. Its assertions are unchanged, in line with the guardrail that #7413's pinned hook count/order stays authoritative. What changed is its comment, which said the re-timing question was "filed rather than folded in here"; that question is now ruled, so the comment records the ruling and notes that changing the expectation would need the ruling reopened. Left as-is, the next reader would have taken a decided contract for an open question.

Changeset

patch on @objectstack/spec and @objectstack/objectql, not skip-changeset. The diff is not docs-only: JSDoc in two published packages ships in their .d.ts and is what an author sees on hover, so the change is user-visible on the package surface. .changeset/after-hook-in-transaction-semantics.md.

Verification

Run in a dedicated worktree off origin/main @ 211abdb, build closure first.

  • pnpm --filter '@objectstack/spec^...' --filter '@objectstack/objectql^...' build — green.
  • pnpm --filter @objectstack/spec --filter @objectstack/objectql typecheck — green (spec tsc + check:scripts-typecheck + check:test-typecheck; objectql tsc).
  • pnpm --filter @objectstack/objectql test178 files / 3149 tests passed, re-run after the comment edit with the same result.
  • pnpm --filter @objectstack/spec test374 files / 9802 tests passed.
  • pnpm --filter @objectstack/spec check:generatedall 13 generated artifacts up to date, incl. check:docs over content/docs/references/**; no regeneration needed.
  • Card gates: check:adr-anchors, check:docs-audit-scope, check:durability-log-level, check:engine-double-contract, check:quick-reference-counts, check:role-word, check:stack-collection-maps — all green. check:doc-formula-expressions is not a root script (root suggests check:doc-authoring); it lives in @objectstack/lint and was run as pnpm --filter @objectstack/lint check:doc-formula-expressions — green (24 self-test cases; 22 formula examples across 390 files; 9 spec TSDoc @examples).
  • node scripts/check-nul-bytes.mjs — OK; plus a control-character self-scan over the three edited source/doc files, clean.

No reverse verification is meaningful here: the change adds no predicate that could go red, and the behaviour it documents is already pinned by the #7413 test this PR leaves assertion-identical. Reporting that rather than manufacturing a direction.


Generated by Claude Code

#7477)

`afterInsert`/`afterUpdate`/`afterDelete` are dispatched before the enclosing
transaction commits. What that guarantees was never written down, so it is now
declared per the maintainer ruling on #7477 (Option 1): an `after*` hook means
"the write has been requested and will happen unless this unit of work is
undone", not "the write happened" — a hook with side effects outside the engine
is responsible for tolerating a rollback.

Zero behaviour change. The statement lands as JSDoc on `HookEvent` and
`HookEventType` in @objectstack/spec, on `DISPATCHABLE_HOOK_EVENTS`,
`HookHandler` and `triggerHooks` in @objectstack/objectql, and as a new section
on content/docs/automation/hooks.mdx. The existing #7413 pin already asserted
this ordering; its comment now records the ruling instead of leaving the
question open — its assertions are unchanged.
@vercel

vercel Bot commented Aug 11, 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 11, 2026 4:00am

Request Review

@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.

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 protocol:data size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

afterDelete fires inside a rollback-able transaction, so a rolled-back cascade leaves a hook that fired for a row that still exists

1 participant