Skip to content

fix(plugin-auth): re-run membership backfill when app seeding settles (#2996)#3000

Merged
os-zhuang merged 1 commit into
mainfrom
claude/plugin-auth-membership-backfill-eyjsic
Jul 16, 2026
Merged

fix(plugin-auth): re-run membership backfill when app seeding settles (#2996)#3000
os-zhuang merged 1 commit into
mainfrom
claude/plugin-auth-membership-backfill-eyjsic

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Context

Closes #2996.

The ADR-0093 D6 membership backfill is the only safety net for users created by app seeds: seeds insert sys_user via a raw engine.insert (system context), which bypasses better-auth's user.create.after reconcileMembership hook. The backfill ran only once, on kernel:ready.

But AppPlugin's inline seed races an 8s budget (OS_INLINE_SEED_BUDGET_MS); on overflow the seed continues in the background past kernel:ready (and even kernel:listening), attached only with a .catch. Users seeded after the backfill ran therefore stayed member-less (empty active org at login) in single-org auto mode until the next restart re-ran the backfill.

This is issue #2996's fix candidate 1 (re-run backfill on seed settle), chosen because it stays entirely within ADR-0093 D6 semantics — the backfill is already idempotent, failure-isolated, bounded, and opt-out-able.

Changes

  • packages/spec/src/contracts/plugin-lifecycle-events.ts — adds a new app:seeded lifecycle event ({ appId, overBudget }) and corrects the kernel:bootstrapped docstring, which wrongly claimed seed data had settled by that anchor.
  • packages/runtime/src/app-plugin.ts — emits app:seeded when the inline seed settles: overBudget: false on inline completion, overBudget: true chained after the background continuation (the post-kernel:ready window). Emission is defensive (mirrors emitCatalogEvent): no-ops when the kernel has no trigger() and swallows trigger errors.
  • packages/plugins/plugin-auth/src/auth-plugin.ts — extracts the backfill handler into a shared runBackfill(source) on a serializing promise chain, registered for both kernel:ready and app:seeded under the existing OS_SKIP_MEMBERSHIP_BACKFILL guard. Serialization prevents overlapping triggers (multiple app bundles) from racing the same scan.
  • packages/core/src/kernel.ts, lite-kernel.ts — comment-only: correct the mirrored "seed data is ready" kernel:bootstrapped note.
  • docs/adr/0093-...md — amends D6 (Proposed) with the app:seeded re-run.
  • .changeset/membership-backfill-app-seeded.md — spec minor (additive event), runtime + plugin-auth patch.

Behavior / compatibility

  • No change when a seed completes within budget (one extra idempotent, quiet backfill pass), in multi-tenant mode (no inline seed → no emission), or under invite-only (backfill self-refuses).
  • OS_SKIP_MEMBERSHIP_BACKFILL=1 still opts out of both the kernel:ready and app:seeded runs.
  • Re-run is idempotent by construction (yields to existing sys_member rows, unique-index protected).

Testing

  • New packages/runtime/src/app-plugin.seed.test.ts (5 tests): emits overBudget: true only after a background seed finishes past the budget; emits overBudget: false on inline completion; no emission without seed datasets; no throw when trigger() is absent; no inline seed/emission in multi-tenant mode.
  • Extended packages/plugins/plugin-auth/src/auth-plugin.test.ts (3 tests): registers the app:seeded hook; binds a user seeded after kernel:ready when app:seeded fires (the plugin-auth: 后台溢出的种子用户可能错过一次性 membership backfill(单组织 auto 模式,重启自愈) #2996 scenario); OS_SKIP_MEMBERSHIP_BACKFILL=1 disables the re-run.
  • pnpm --filter @objectstack/runtime test → 528 passed. pnpm --filter @objectstack/plugin-auth test → 443 passed. pnpm --filter @objectstack/spec build clean. ESLint clean on changed files.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VcDBuUsuX7FBuUJuLuX8Pi


Generated by Claude Code

…#2996)

The ADR-0093 D6 membership backfill is the only safety net for users created
by app seeds — a raw `engine.insert` into `sys_user` bypasses better-auth's
`user.create.after` reconciler — but it ran only once on `kernel:ready`. When a
seed bundle overruns its inline budget (`OS_INLINE_SEED_BUDGET_MS`, default 8s)
it finishes in the background AFTER `kernel:ready`, leaving its users member-less
in single-org `auto` mode until the next restart re-ran the backfill.

AppPlugin now emits a new `app:seeded` lifecycle event when an app's inline seed
settles (success, partial, or fallback), carrying `{ appId, overBudget }` —
`overBudget: true` marks the post-`kernel:ready` background case. plugin-auth
subscribes and re-runs the idempotent `backfillMemberships` on that signal,
serializing runs so overlapping triggers don't trip the unique index into warn
noise. No behavior change when a seed completes within budget, in multi-tenant
mode, or under `invite-only`; `OS_SKIP_MEMBERSHIP_BACKFILL=1` still opts out.

Also corrects the stale `kernel:bootstrapped` docstrings (spec + both kernels)
that claimed seed data had settled by that anchor, which is false under budget
overflow.

Tests: new runtime seed-settle emission coverage (app-plugin.seed.test.ts) and
plugin-auth re-run coverage (auth-plugin.test.ts).

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

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 16, 2026 3:23am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m labels Jul 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/core, @objectstack/plugin-auth, @objectstack/runtime, @objectstack/spec.

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

  • content/docs/ai/actions-as-tools.mdx (via @objectstack/core)
  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/knowledge-rag.mdx (via @objectstack/core)
  • content/docs/ai/natural-language-queries.mdx (via @objectstack/core)
  • 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/runtime, @objectstack/spec)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime, 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/core, @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 packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/core, packages/runtime, packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime, @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 @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/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/core)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/cli.mdx (via @objectstack/plugin-auth, @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/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/runtime, @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/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/core)
  • 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 packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/core, @objectstack/plugin-auth, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/core)
  • content/docs/permissions/authentication.mdx (via @objectstack/core, @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/core, @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/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/permissions/sso.mdx (via @objectstack/plugin-auth)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/anatomy.mdx (via @objectstack/core)
  • content/docs/plugins/development.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/core, @objectstack/plugin-auth, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/core, @objectstack/plugin-auth, @objectstack/runtime, @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/core, @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/core, @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/core, @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @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/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/releases/implementation-status.mdx (via @objectstack/core, @objectstack/plugin-auth, @objectstack/runtime, @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/v9.mdx (via @objectstack/plugin-auth, @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)

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.

@os-zhuang os-zhuang marked this pull request as ready for review July 16, 2026 03:28
@os-zhuang os-zhuang merged commit 23925e9 into main Jul 16, 2026
17 checks passed
@os-zhuang os-zhuang deleted the claude/plugin-auth-membership-backfill-eyjsic branch July 16, 2026 03:29
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/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

plugin-auth: 后台溢出的种子用户可能错过一次性 membership backfill(单组织 auto 模式,重启自愈)

2 participants