Problem
The same concept — the caller's active organization — is surfaced to third-party app developers under two different names, split by which authoring mode they're in. A developer writing a metadata app + hooks has to internalize the hidden equation tenantId === organizationId to move between the surfaces.
This is a developer-experience / naming issue only. It is not about the internal kernel or driver layer (see Non-goals). It surfaced while tracing the tenant-scope layer for #3249.
Where a developer sees each name today
| What the developer is doing | Name they use | Value is |
|---|
Object metadata (.object.yml) | tenancy.enabled / tenancy.tenantField | mechanism / column knob |
| Field definitions & query filters | organization_id | the org |
| JS hook — read the current org | ctx.session.tenantId | the org (named "tenant") |
JS hook — ctx.user shortcut | { id, name, email } — no org field at all | — |
| RLS / sharing / permission expressions | current_user.organizationId | the org |
| Seed data | organizationId | the org |
The value the developer actually cares about is the org, and it is called organization_id / organizationIdeverywhere except the JS-hook session, which calls it tenantId. Worse, the ergonomic ctx.user shortcut has no org field, so a hook author who needs "the current org to filter by" is forced onto ctx.session.tenantId and must know that tenant == org.
Evidence (spec/engine source)
packages/objectql/src/engine.ts — buildSession() emits session.tenantId (from execCtx.tenantId); ctx.user shortcut is only { id, name, email }.packages/spec/src/data/hook.zod.ts — HookContext.session exposes tenantId; the user object has no org field. Test hook.test.ts asserts context.session?.tenantId.packages/spec/src/identity/eval-user.zod.ts:104 — RLS current_user uses organizationId ("Active organization ID (null = platform/unscoped)").packages/spec/src/identity/identity.zod.ts:268 — organizationId ("Scoped organization ID").packages/spec/src/data/seed-loader.zod.ts — seed rows use organizationId.packages/spec/src/system/metadata-persistence.zod.ts:103 — literally comments that Tenant ID is an "alias of organizationId in some contexts".packages/spec/src/kernel/execution-context.zod.ts:45 — ExecutionContext.tenantId is documented as "resolved from session.activeOrganizationId", i.e. it is always the org.
Proposed direction — additive, non-breaking, converge the dev surface on organization
Because org is already the majority term on the developer surface (column, current_user, seed) and matches the DB column organization_id, converge the JS-hook surface onto it. Add the org name; keep the old one as a deprecated alias. Do not rename internals.
- Add
organizationId to the hook ctx.user shortcut (highest value). buildSession() already holds execCtx.tenantId — also expose it as user.organizationId. A hook author then writes ctx.user.organizationId, matching current_user.organizationId (RLS) and organization_id (filters) with zero relearning. - Add
organizationId to ctx.session as the blessed name; keep session.tenantId as a deprecated alias. Both carry the same value; new docs/examples teach only organizationId. - Leave the
tenancy.* object-metadata knob as-is, but document it clearly: tenancy is the isolation mechanism (on/off + custom tenantField); the value you read/write is your organization_id column.
Non-goals (do NOT touch)
ExecutionContext.tenantId, DriverOptions.tenantId, SqlDriver.applyTenantScope / tenantFieldByTable, TenancyConfig.tenantField. These are the generic tenancy abstraction at the driver layer — the isolation column is configurable (tenant_id / workspace_id / organization_id), and the same DriverOptions.tenantId legitimately carries an environment id in per-environment kernels (e.g. service-ai tools stamp tenantId: ctx.environmentId). Renaming these would (a) bake an "org" assumption into a generic layer and (b) be a wide public-API break. Keep them generic; only the developer-facing hook surface changes.
Acceptance criteria
Notes
Problem
The same concept — the caller's active organization — is surfaced to third-party app developers under two different names, split by which authoring mode they're in. A developer writing a metadata app + hooks has to internalize the hidden equation
tenantId === organizationIdto move between the surfaces.This is a developer-experience / naming issue only. It is not about the internal kernel or driver layer (see Non-goals). It surfaced while tracing the tenant-scope layer for #3249.
Where a developer sees each name today
.object.yml)tenancy.enabled/tenancy.tenantFieldorganization_idctx.session.tenantIdctx.usershortcut{ id, name, email }— no org field at allcurrent_user.organizationIdorganizationIdThe value the developer actually cares about is the org, and it is called
organization_id/organizationIdeverywhere except the JS-hooksession, which calls ittenantId. Worse, the ergonomicctx.usershortcut has no org field, so a hook author who needs "the current org to filter by" is forced ontoctx.session.tenantIdand must know thattenant == org.Evidence (spec/engine source)
packages/objectql/src/engine.ts—buildSession()emitssession.tenantId(fromexecCtx.tenantId);ctx.usershortcut is only{ id, name, email }.packages/spec/src/data/hook.zod.ts—HookContext.sessionexposestenantId; theuserobject has no org field. Testhook.test.tsassertscontext.session?.tenantId.packages/spec/src/identity/eval-user.zod.ts:104— RLScurrent_userusesorganizationId("Active organization ID (null = platform/unscoped)").packages/spec/src/identity/identity.zod.ts:268—organizationId("Scoped organization ID").packages/spec/src/data/seed-loader.zod.ts— seed rows useorganizationId.packages/spec/src/system/metadata-persistence.zod.ts:103— literally comments that Tenant ID is an "alias of organizationId in some contexts".packages/spec/src/kernel/execution-context.zod.ts:45—ExecutionContext.tenantIdis documented as "resolved fromsession.activeOrganizationId", i.e. it is always the org.Proposed direction — additive, non-breaking, converge the dev surface on
organizationBecause
orgis already the majority term on the developer surface (column,current_user, seed) and matches the DB columnorganization_id, converge the JS-hook surface onto it. Add the org name; keep the old one as a deprecated alias. Do not rename internals.organizationIdto the hookctx.usershortcut (highest value).buildSession()already holdsexecCtx.tenantId— also expose it asuser.organizationId. A hook author then writesctx.user.organizationId, matchingcurrent_user.organizationId(RLS) andorganization_id(filters) with zero relearning.organizationIdtoctx.sessionas the blessed name; keepsession.tenantIdas a deprecated alias. Both carry the same value; new docs/examples teach onlyorganizationId.tenancy.*object-metadata knob as-is, but document it clearly:tenancyis the isolation mechanism (on/off + customtenantField); the value you read/write is yourorganization_idcolumn.Non-goals (do NOT touch)
ExecutionContext.tenantId,DriverOptions.tenantId,SqlDriver.applyTenantScope/tenantFieldByTable,TenancyConfig.tenantField. These are the generic tenancy abstraction at the driver layer — the isolation column is configurable (tenant_id/workspace_id/organization_id), and the sameDriverOptions.tenantIdlegitimately carries an environment id in per-environment kernels (e.g.service-aitools stamptenantId: ctx.environmentId). Renaming these would (a) bake an "org" assumption into a generic layer and (b) be a wide public-API break. Keep them generic; only the developer-facing hook surface changes.Acceptance criteria
ctx.user.organizationIdis available in JS hooks (all hook events that today populatesession), typed inHookContext(spec).ctx.session.organizationIdis available;ctx.session.tenantIdstill works but is marked deprecated in its TSDoc.execCtx.tenantId(the resolved active org). Unit test covering a populated org and the anonymous/undefined case.organizationId(dropsession.tenantIdfrom examples).organization_id, shared DB) vs environment/database-per-tenant (service-tenant,driver-turso— where "tenant" = environment).Notes
tenantId, so this is purely about naming clarity for multi-org (cloud +@objectstack/organizations) app developers.