Skip to content

fix(seed-loader): keep the per-org tenant stamp instead of resolving it as a natural key - #4644

Merged
os-zhuang merged 1 commit into
mainfrom
claude/hotcrm-multi-tenant-single-db-61tyos
Aug 2, 2026
Merged

fix(seed-loader): keep the per-org tenant stamp instead of resolving it as a natural key#4644
os-zhuang merged 1 commit into
mainfrom
claude/hotcrm-multi-tenant-single-db-61tyos

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

What was broken

In a multi-org deployment the per-organization seed replay landed every row org-less. A freshly created organization therefore booted with a database full of data that nobody could see: the tenant wall (organization_id = <active org>) hides a NULL-org row from all members, including that organization's own owner.

The seed summary reads clean while this happens — a dropped reference is counted separately from rejected rows — so there is no signal at boot.

Root cause

The stamp and the reference pass disagree about what organization_id holds.

SeedLoaderService writes config.organizationId — the replay target's id — into the record. The reference pass then sees a field declared as a lookup → sys_organization, treats the value as a natural key, and probes sys_organization.name for it. That misses, and a missed reference is dropped rather than kept, taking the tenant attribution with it.

The id fallback probe cannot rescue it either: under per-tenant replay every probe is AND-scoped with organization_id = <target org>, and sys_organization — being the tenant table itself — carries no such column, so that probe matches nothing by construction.

Why it stayed hidden

The id shape. looksLikeInternalId recognises UUID and Mongo ObjectId and short-circuits resolution for both, so any fixture minting UUID organization ids passed. Every organization better-auth actually creates is org_<base36> — including the default organization ensureDefaultOrganization bootstraps on first boot — and that shape is not recognised.

So the defect fired on real deployments and on nothing else.

The fix

The loader now remembers that it wrote the stamp itself and skips resolution for that one field. A seed that authors organization_id explicitly still goes through resolution, so naming an organization by its natural key keeps working.

Narrow on purpose: widening looksLikeInternalId to accept org_* would be the runtime-side "accept another dialect" fallback the repo's contract-first doctrine rules out. The loader second-guessing a value it just wrote itself is the actual defect, so that is what changed.

Verification

  • packages/objectql/src/seed-loader-org-stamp.test.ts — 3 new cases: the better-auth id shape survives (fails without the fix), the UUID shape still survives, and a seed-authored natural key still resolves.
  • End-to-end: apps/ee-tenant-crm-showcase in the cloud repo (companion PR) boots two organizations over one database through @objectstack/verify. 10/10 pass with this fix; the two seed-attribution cases fail against the unfixed framework — confirmed by rebuilding at the pre-fix commit and re-running.

🤖 Generated with Claude Code

https://claude.ai/code/session_019TYoxKa8yFLiDDh7tkBqtu


Generated by Claude Code

@vercel

vercel Bot commented Aug 2, 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 2, 2026 1:59pm

Request Review

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

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/metadata-protocol.

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

  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/metadata-protocol)
  • content/docs/releases/v9.mdx (via @objectstack/metadata-protocol)

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.

…it as a natural key

The per-organization seed replay landed every row org-less, so a freshly
created organization booted with data the tenant wall hides from everyone —
including that organization's own owner.

The stamp and the reference pass disagreed about what `organization_id` holds.
The loader writes `config.organizationId` — the replay target's id. The
reference pass then sees a field declared as a lookup to `sys_organization`,
treats the value as a natural key, probes `sys_organization.name`, misses, and
drops the field; a dropped reference takes the tenant attribution with it. The
`id` fallback probe cannot rescue it: under replay every probe is AND-scoped
with `organization_id = <target org>`, and `sys_organization` is the tenant
table itself and carries no such column.

The id SHAPE is what kept this hidden. `looksLikeInternalId` short-circuits
UUID and ObjectId, so fixtures minting UUID org ids passed. Every organization
better-auth creates — including the default org `ensureDefaultOrganization`
bootstraps — is `org_<base36>`, which it does not recognise, so the defect
fired on real deployments and nowhere else.

The loader now remembers it wrote the stamp and skips resolution for that field
only. A seed authoring `organization_id` itself still resolves, so naming an
organization by natural key keeps working.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019TYoxKa8yFLiDDh7tkBqtu
@os-zhuang
os-zhuang force-pushed the claude/hotcrm-multi-tenant-single-db-61tyos branch from b5c2321 to ca30220 Compare August 2, 2026 13:59
@os-zhuang
os-zhuang marked this pull request as ready for review August 2, 2026 14:31
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 2, 2026
Merged via the queue into main with commit 666f542 Aug 2, 2026
21 checks passed
@os-zhuang
os-zhuang deleted the claude/hotcrm-multi-tenant-single-db-61tyos branch August 2, 2026 14:38
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.

2 participants