Skip to content

fix(platform-objects): organization_id optional so BU/Team create in single-tenant (ADR-0057)#2178

Merged
xuyushun441-sys merged 1 commit into
mainfrom
fix/adr-0057-org-id-optional
Jun 22, 2026
Merged

fix(platform-objects): organization_id optional so BU/Team create in single-tenant (ADR-0057)#2178
xuyushun441-sys merged 1 commit into
mainfrom
fix/adr-0057-org-id-optional

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

You reported: creating a Business Unit in the Setup console errors.

Root cause

sys_business_unit.organization_id (and sys_team.organization_id) are required: true, but a single-tenant deployment has no sys_organization row and nothing auto-stamps one (OrgScopingPlugin is multi-tenant-only). So every create fails:

VALIDATION_FAILED: organization_id (required)   →  HTTP 400

This made the whole org-scoped identity graph uncreatable single-tenant — directly contradicting "BU is usable in the open/single-tenant edition" (ADR-0057 addendum).

Fix

Make organization_id optional on sys_business_unit and sys_team:

  • single-tenantnull (consistent with "no org concept");
  • multi-tenant → still auto-stamped by OrgScopingPlugin; tenant_isolation RLS hides any null-org row (fail-closed) → no cross-tenant exposure.

Pure validation change (the columns are already nullable) — no migration.

sys_member / sys_invitation carry the same flag but are created only via better-auth org flows (which always supply an org) and aren't directly creatable in single-tenant Setup — left unchanged.

Verified (live os dev, single-tenant)

POST /data/sys_business_unit {name,kind}  →  201  (organization_id: null)
POST /data/sys_team {name}                →  201  (organization_id: null)

Before the fix both returned 400. Plus a dogfood regression test (boots single-tenant, creates both).

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jun 22, 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 Jun 22, 2026 6:14am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/s labels Jun 22, 2026
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/dogfood, @objectstack/platform-objects.

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

  • content/docs/concepts/packages.mdx (via @objectstack/platform-objects)
  • content/docs/concepts/setup-app.mdx (via @objectstack/platform-objects)
  • content/docs/guides/packages.mdx (via @objectstack/platform-objects)

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.

…d optional (single-tenant create)

Single-tenant has no sys_organization row and no auto-stamp (org-scoping is multi-tenant-only), so a required organization_id made these objects uncreatable (VALIDATION_FAILED). Optional now: single-tenant = null; multi-tenant still auto-stamps via OrgScopingPlugin and tenant-isolation RLS hides null-org rows (fail-closed). +dogfood regression test. Verified live on os dev (BU + Team create 201 single-tenant).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@xuyushun441-sys
xuyushun441-sys force-pushed the fix/adr-0057-org-id-optional branch from b155607 to 27abccd Compare June 22, 2026 06:11
@xuyushun441-sys
xuyushun441-sys merged commit 0df063e into main Jun 22, 2026
17 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the fix/adr-0057-org-id-optional branch June 22, 2026 06:23
xuyushun441-sys added a commit that referenced this pull request Jun 22, 2026
…(ADR-0057) (#2182)

1) sys_member/sys_invitation organization_id -> optional (same class as #2178; single-tenant has no org/auto-stamp; multi-tenant still stamps + RLS hides null-org). 2) BusinessUnitGraphService.headOf() add missing orgScope() — it runs under SYSTEM_CTX so orgScope is the only isolation; without it headOf(buId) leaked a BU's manager_user_id across organizations (inconsistent with descendants()). +regression test (org1 service must not read an org2 BU's head).

Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
xuyushun441-sys added a commit that referenced this pull request Jun 22, 2026
#2196)

The metadata→DB sync was additive-only (create table / add column), so any
non-additive metadata change — relax `required`, change type/length, drop or
rename a field — silently diverged from an existing database. The physical
column won at write time, surfacing a misleading `organization_id is required`
400 even though `/meta` reported the field optional.

Root-cause note: that 400 is NOT a validator bug. `record-validator` already
reads `required` from metadata and passes once the field is optional; the 400
is a DB NOT NULL violation that `rest-server` paraphrases as "is required".
So the fix is reconciling the physical schema + de-misleading the message,
not changing the validator's source of truth.

P1 — detection + warnings (schema-drift.ts, wired into SqlDriver.initObjects):
  diff metadata vs physical columns, categorise safe / needs_confirm /
  destructive (reusing SchemaDiffEntry). Boot warns once per divergence with an
  actionable `os migrate` hint. rest-server's NOT NULL translation now carries a
  drift-aware `hint` (VALIDATION_FAILED / fields envelope unchanged for back-compat).

P2 — dev auto-reconcile (SqlDriverConfig.autoMigrate:'safe', wired into
  serve/dev in dev only): auto-applies the loosening subset (relax NOT NULL,
  widen varchar) so an existing dev DB self-heals on restart. Never destructive;
  force-disabled under NODE_ENV=production.

P3 — os migrate plan/apply (cli/commands/migrate/*): categorised dry-run +
  reconcile, --allow-destructive gate, confirm prompt, --json. SQLite reconciles
  via the official table-rebuild (copy→swap, data preserved); Postgres/MySQL
  alter in place. Only examines objects in the loaded artifact; never auto-drops
  a table absent from metadata.

Tests: driver-sql +12 (incl. #2178 repro self-heal with data preserved, prod
guard, destructive gating, PG/MySQL DDL-gen), cli integration (real
createStandaloneStack boot → detect → apply → in-sync), rest hint. Full build green.

Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants