fix(plugin-auth): re-run membership backfill when app seeding settles (#2996)#3000
Merged
Merged
Conversation
…#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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 4 package(s): 113 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
Closes #2996.
The ADR-0093 D6 membership backfill is the only safety net for users created by app seeds: seeds insert
sys_uservia a rawengine.insert(system context), which bypasses better-auth'suser.create.afterreconcileMembershiphook. The backfill ran only once, onkernel:ready.But
AppPlugin's inline seed races an 8s budget (OS_INLINE_SEED_BUDGET_MS); on overflow the seed continues in the background pastkernel:ready(and evenkernel:listening), attached only with a.catch. Users seeded after the backfill ran therefore stayed member-less (empty active org at login) in single-orgautomode 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 newapp:seededlifecycle event ({ appId, overBudget }) and corrects thekernel:bootstrappeddocstring, which wrongly claimed seed data had settled by that anchor.packages/runtime/src/app-plugin.ts— emitsapp:seededwhen the inline seed settles:overBudget: falseon inline completion,overBudget: truechained after the background continuation (the post-kernel:readywindow). Emission is defensive (mirrorsemitCatalogEvent): no-ops when the kernel has notrigger()and swallows trigger errors.packages/plugins/plugin-auth/src/auth-plugin.ts— extracts the backfill handler into a sharedrunBackfill(source)on a serializing promise chain, registered for bothkernel:readyandapp:seededunder the existingOS_SKIP_MEMBERSHIP_BACKFILLguard. 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:bootstrappednote.docs/adr/0093-...md— amends D6 (Proposed) with theapp:seededre-run..changeset/membership-backfill-app-seeded.md— specminor(additive event), runtime + plugin-authpatch.Behavior / compatibility
invite-only(backfill self-refuses).OS_SKIP_MEMBERSHIP_BACKFILL=1still opts out of both thekernel:readyandapp:seededruns.sys_memberrows, unique-index protected).Testing
packages/runtime/src/app-plugin.seed.test.ts(5 tests): emitsoverBudget: trueonly after a background seed finishes past the budget; emitsoverBudget: falseon inline completion; no emission without seed datasets; no throw whentrigger()is absent; no inline seed/emission in multi-tenant mode.packages/plugins/plugin-auth/src/auth-plugin.test.ts(3 tests): registers theapp:seededhook; binds a user seeded afterkernel:readywhenapp:seededfires (the plugin-auth: 后台溢出的种子用户可能错过一次性 membership backfill(单组织 auto 模式,重启自愈) #2996 scenario);OS_SKIP_MEMBERSHIP_BACKFILL=1disables the re-run.pnpm --filter @objectstack/runtime test→ 528 passed.pnpm --filter @objectstack/plugin-auth test→ 443 passed.pnpm --filter @objectstack/spec buildclean. ESLint clean on changed files.🤖 Generated with Claude Code
https://claude.ai/code/session_01VcDBuUsuX7FBuUJuLuX8Pi
Generated by Claude Code