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

vercelBot commented Jul 16, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 16, 2026 3:23am

Request Review

@github-actionsgithub-actionsBot 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 mainJul 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants

@os-zhuang@claude