Skip to content

fix(runtime,cloud-connection): multi-tenant seed replay covers every source, not just the first (#3453) - #3478

Merged
os-zhuang merged 1 commit into
mainfrom
claude/competent-dubinsky-3b3f42
Jul 25, 2026
Merged

fix(runtime,cloud-connection): multi-tenant seed replay covers every source, not just the first (#3453)#3478
os-zhuang merged 1 commit into
mainfrom
claude/competent-dubinsky-3b3f42

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

What & why

Fixes#3453. In multi-tenant deployments (enterprise @objectstack/organizations) a brand-new org gets its own private copy of demo data by replaying the kernel's seed-datasets list on the sys_organization insert. That list is supposed to hold the union of every seed source — every config-declared app plus every marketplace package — but two framework traps (the same pair #3444 fixed for seed-summary) silently shrank it to just the first source:

  1. No .kernel handle. The standard PluginContext (packages/core/src/kernel.ts:76) exposes getService/registerService but has no .kernel property, so (ctx as any).kernel?.getService('seed-datasets') always read undefined. Each source then saw "nothing registered" and overwrote the list with only its own datasets.
  2. Duplicate-register throw.registerService (kernel.ts:195) throws on a duplicate name, so the second source's re-register was swallowed by the surrounding try/catch — its datasets (and, for a config app, its replayer) silently lost.

Net effect: with two config apps, or a config app + marketplace packages, a new org replayed only the first app's seeds.

The fix — register-once-then-mutate (pattern from #3444's recordSeedOutcome)

New packages/runtime/src/seed-datasets.ts helper:

  • mergeSeedDatasets(ctx, datasets) — registers one shared array the first time and mutates it in place for every later source, reading through the context's own resolver first (ctx.getService), kernel second. Never re-registers → no duplicate-register throw; always accumulates → no clobber.
  • readSeedDatasets(ctx) — reads the live shared array.
  • registerSeedReplayerOnce(ctx, replayer) — registers the per-org replayer once; later config apps reuse it.

Wired into both seams the issue names:

  • packages/runtime/src/app-plugin.ts — the config-app seed path now mergeSeedDatasets(...) into the shared array, and the per-org replayer reads readSeedDatasets(ctx)at invoke time instead of the captured merged snapshot, so an org created after a later app/marketplace install still replays their seeds.
  • packages/cloud-connection/src/marketplace-install-local-plugin.ts — the marketplace merge now routes through the runtime helper via a lazy import('@objectstack/runtime') + typeof-guard (mirroring the existing recordSeedSummary), with an equivalent inline fallback so the two test files that mock @objectstack/runtime without the helper don't crash.

Testing

packages/runtime/src/seed-datasets.test.ts (7 tests):

  • Helper units against a faithful kernel fake (getService throws on miss, registerService throws on dupe, no .kernel — mirrors seed-summary.test.ts): reads the live service despite no .kernel (defect ①); a second source accumulates with no duplicate-register throw (defect ②); the live array grows for a captured reference; registerSeedReplayerOnce registers once then reuses.
  • Seam: boots the realAppPlugin twice + a marketplace-style merge and invokes the registered seed-replayer, asserting it replays the full union[crm_account, crm_contact, hot_lead] in source order, tenant-scoped.

Red-proof: against the pre-fix app-plugin.ts the seam test fails exactly as #3453 describes — the replayer drops crm_contact (the second config app's seeds).

Results:

  • @objectstack/runtime — 44 passed (app-plugin, app-plugin.seed, seed-datasets, seed-summary)
  • @objectstack/cloud-connection — 18 passed (all marketplace-install-local-*, incl. the two that mock the runtime module and exercise the fallback)
  • forced build (DTS type-check) green for both packages

Scope note

This repo only carries seam-level coverage. True multi-tenant end-to-end coverage requires the enterprise @objectstack/organizations plugin (which drives the sys_organization-insert replay), and that lives in the cloud repo — out of tree here.

🤖 Generated with Claude Code

…source, not just the first (#3453)
In multi-tenant mode a new org replays the kernel's `seed-datasets` list on the
`sys_organization` insert, so that list must hold the union of every seed source
(every config app + every marketplace package). Two framework traps — the same
pair #3444 fixed for seed-summary — shrank it to just the first source:
- the standard PluginContext has no `.kernel` handle, so
`(ctx as any).kernel?.getService('seed-datasets')` always read undefined and
each source clobbered the list with only its own datasets; and
- `registerService` throws on a duplicate name, so the second source's
re-register was swallowed and its datasets (and, for a config app, its
replayer) were silently lost.
`seed-datasets` is now a single shared array, registered once and mutated in
place by every source via a new `mergeSeedDatasets` helper that reads through the
context's own resolver first. AppPlugin's per-org replayer reads that live list
at invoke time — not a captured snapshot — and is itself registered once and
reused by later config apps, so a new org replays the full union.
Seam-level unit tests only (accumulation across app + marketplace sources; the
replayer reads the live union); true multi-tenant e2e needs the enterprise
`@objectstack/organizations` plugin, which lives in the cloud repo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercelBot commented Jul 25, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 25, 2026 1:31am

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling size/m labels Jul 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cloud-connection, @objectstack/runtime.

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

  • content/docs/api/index.mdx(via @objectstack/runtime)
  • content/docs/api/wire-format.mdx(via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx(via @objectstack/runtime)
  • content/docs/concepts/north-star.mdx(via packages/runtime)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/runtime)
  • content/docs/deployment/index.mdx(via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx(via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx(via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx(via @objectstack/runtime)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/runtime)
  • content/docs/permissions/authentication.mdx(via @objectstack/runtime)
  • content/docs/permissions/authorization.mdx(via packages/runtime)
  • content/docs/plugins/packages.mdx(via @objectstack/runtime)
  • content/docs/protocol/kernel/http-protocol.mdx(via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/runtime)
  • content/docs/releases/implementation-status.mdx(via @objectstack/runtime)

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 merged commit 810a3a2 into mainJul 25, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/competent-dubinsky-3b3f42 branch July 25, 2026 02:16
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

1 participant

@os-zhuang