Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .changeset/v11-remove-env-aliases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
"@objectstack/types": major
"@objectstack/objectql": major
"@objectstack/cli": major
---

Remove ObjectStack's own legacy env-var aliases (11.0); ecosystem-standard names stay.

The framework's renamed env vars no longer accept their old ObjectStack names —
rename them:

| removed legacy name | use |
|---|---|
| `OS_MULTI_TENANT` | `OS_MULTI_ORG_ENABLED` |
| `OBJECTSTACK_METADATA_WRITABLE` | `OS_METADATA_WRITABLE` |
| `OS_AUTH_BASE_URL`, `AUTH_BASE_URL` | `OS_AUTH_URL` |

**Ecosystem-standard names are NOT removed** — they remain accepted (and no longer
emit a deprecation warning, since they are permanent conventions, not legacy):
`DATABASE_URL`, `AUTH_SECRET`, `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`, `PORT`,
`CORS_*`, `LOG_LEVEL`, `ROOT_DOMAIN`, `MCP_SERVER_*`. The generic
`readEnvWithDeprecation` helper is unchanged.
8 changes: 4 additions & 4 deletions packages/adapters/hono/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,15 @@ export function createHonoApp(options: ObjectStackHonoOptions): Hono {
// OS_CORS_CREDENTIALS – "false" to disallow credentials (default: true)
// OS_CORS_MAX_AGE – preflight cache seconds (default: 86400)
// (legacy CORS_* names still honoured with a deprecation warning)
const corsDisabledByEnv = readEnvWithDeprecation('OS_CORS_ENABLED', 'CORS_ENABLED') === 'false';
const corsDisabledByEnv = readEnvWithDeprecation('OS_CORS_ENABLED', 'CORS_ENABLED', { silent: true }) === 'false';
if (options.cors !== false && !corsDisabledByEnv) {
const corsOpts = typeof options.cors === 'object' ? options.cors : {};
const enabled = corsOpts.enabled ?? true;

if (enabled) {
// Resolve origins: options > env > default '*'
let configuredOrigin: string | string[];
const corsOriginEnv = readEnvWithDeprecation('OS_CORS_ORIGIN', 'CORS_ORIGIN');
const corsOriginEnv = readEnvWithDeprecation('OS_CORS_ORIGIN', 'CORS_ORIGIN', { silent: true });
if (corsOpts.origin) {
configuredOrigin = corsOpts.origin;
} else if (corsOriginEnv) {
Expand All @@ -142,8 +142,8 @@ export function createHonoApp(options: ObjectStackHonoOptions): Hono {
configuredOrigin = '*';
}

const credentials = corsOpts.credentials ?? (readEnvWithDeprecation('OS_CORS_CREDENTIALS', 'CORS_CREDENTIALS') !== 'false');
const maxAgeEnv = readEnvWithDeprecation('OS_CORS_MAX_AGE', 'CORS_MAX_AGE');
const credentials = corsOpts.credentials ?? (readEnvWithDeprecation('OS_CORS_CREDENTIALS', 'CORS_CREDENTIALS', { silent: true }) !== 'false');
const maxAgeEnv = readEnvWithDeprecation('OS_CORS_MAX_AGE', 'CORS_MAX_AGE', { silent: true });
const maxAge = corsOpts.maxAge ?? (maxAgeEnv ? parseInt(maxAgeEnv, 10) : 86400);

// When credentials is true, browsers reject wildcard '*' for Access-Control-Allow-Origin.
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/commands/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1099,7 +1099,7 @@ export default class Serve extends Command {

// In dev, fall back to a stable local secret so users don't have
// to set OS_AUTH_SECRET just to try the login/register flow.
const secret = readEnvWithDeprecation('OS_AUTH_SECRET', ['AUTH_SECRET', 'BETTER_AUTH_SECRET'])
const secret = readEnvWithDeprecation('OS_AUTH_SECRET', ['AUTH_SECRET', 'BETTER_AUTH_SECRET'], { silent: true })
?? (isDev ? 'dev-only-insecure-secret-change-me-in-production' : undefined);

// Guard: in cloud-connected runtime mode (e.g. objectos worker)
Expand Down Expand Up @@ -1128,7 +1128,7 @@ export default class Serve extends Command {
} else if (!secret) {
console.warn(chalk.yellow(' ⚠ AuthPlugin skipped — set OS_AUTH_SECRET to enable authentication in production'));
} else {
const baseUrl = readEnvWithDeprecation('OS_AUTH_URL', ['OS_AUTH_BASE_URL', 'AUTH_BASE_URL', 'BETTER_AUTH_URL'])
const baseUrl = readEnvWithDeprecation('OS_AUTH_URL', 'BETTER_AUTH_URL', { silent: true })
?? process.env.OS_BASE_URL
?? `http://localhost:${port}`;

Expand Down Expand Up @@ -1184,7 +1184,7 @@ export default class Serve extends Command {
// must be trusted by better-auth or sign-up/sign-in is
// rejected with "Invalid origin". Mirrors the OS_COOKIE_DOMAIN
// wildcard semantics — they are always set together.
const rootDomain = readEnvWithDeprecation('OS_ROOT_DOMAIN', 'ROOT_DOMAIN')?.trim();
const rootDomain = readEnvWithDeprecation('OS_ROOT_DOMAIN', 'ROOT_DOMAIN', { silent: true })?.trim();
if (rootDomain) {
const wildcard = `https://*.${rootDomain}`;
if (!trustedOrigins.includes(wildcard)) trustedOrigins.push(wildcard);
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export default class Start extends Command {
// ── Database resolution ─────────────────────────────────────────
// Priority: --database > $OS_DATABASE_URL > $DATABASE_URL (legacy) > file:<home>/data/objectstack.db
const databaseUrl = flags.database
?? readEnvWithDeprecation('OS_DATABASE_URL', 'DATABASE_URL')
?? readEnvWithDeprecation('OS_DATABASE_URL', 'DATABASE_URL', { silent: true })
?? `file:${path.join(homeDir, 'data', 'objectstack.db')}`;

const environmentId = flags['environment-id']
Expand All @@ -196,7 +196,7 @@ export default class Start extends Command {
// Quick-start should "just work" without the user having to
// export AUTH_SECRET.
const authSecret = flags['auth-secret']
?? readEnvWithDeprecation('OS_AUTH_SECRET', ['AUTH_SECRET', 'BETTER_AUTH_SECRET'])
?? readEnvWithDeprecation('OS_AUTH_SECRET', ['AUTH_SECRET', 'BETTER_AUTH_SECRET'], { silent: true })
?? readOrCreateAuthSecret(homeDir);

// ── Banner ──────────────────────────────────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/utils/log-level.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,5 @@ export function resolveLogLevel(opts: {
* environment, emitting the standard deprecation warning for the legacy name.
*/
export function readLogLevelEnv(): string | undefined {
return readEnvWithDeprecation('OS_LOG_LEVEL', 'LOG_LEVEL');
return readEnvWithDeprecation('OS_LOG_LEVEL', 'LOG_LEVEL', { silent: true });
}
6 changes: 3 additions & 3 deletions packages/mcp/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ export class MCPServerPlugin implements Plugin {

async init(ctx: PluginContext): Promise<void> {
const config: MCPServerRuntimeConfig = {
name: readEnvWithDeprecation('OS_MCP_SERVER_NAME', 'MCP_SERVER_NAME') ?? this.options.name ?? 'objectstack',
name: readEnvWithDeprecation('OS_MCP_SERVER_NAME', 'MCP_SERVER_NAME', { silent: true }) ?? this.options.name ?? 'objectstack',
version: this.options.version ?? '1.0.0',
transport: (readEnvWithDeprecation('OS_MCP_SERVER_TRANSPORT', 'MCP_SERVER_TRANSPORT') as 'stdio' | 'http') ?? this.options.transport ?? 'stdio',
transport: (readEnvWithDeprecation('OS_MCP_SERVER_TRANSPORT', 'MCP_SERVER_TRANSPORT', { silent: true }) as 'stdio' | 'http') ?? this.options.transport ?? 'stdio',
instructions: this.options.instructions,
logger: ctx.logger,
};
Expand Down Expand Up @@ -118,7 +118,7 @@ export class MCPServerPlugin implements Plugin {
}

// ── Auto-start if configured ──
const shouldStart = this.options.autoStart || readEnvWithDeprecation('OS_MCP_SERVER_ENABLED', 'MCP_SERVER_ENABLED') === 'true';
const shouldStart = this.options.autoStart || readEnvWithDeprecation('OS_MCP_SERVER_ENABLED', 'MCP_SERVER_ENABLED', { silent: true }) === 'true';
if (shouldStart) {
await this.runtime.start();
ctx.logger.info('[MCP] Server started automatically');
Expand Down
4 changes: 2 additions & 2 deletions packages/objectql/src/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,10 @@ export interface SchemaRegistryOptions {
* is additionally INDEXED — single-tenant stacks skip the index since
* nothing ever filters by organization.
*
* Sourced from the `OS_MULTI_TENANT` env var when not explicitly set —
* Sourced from the `OS_MULTI_ORG_ENABLED` env var when not explicitly set —
* matches how the SecurityPlugin and CLI startup banner pick the mode.
* Default is `false` (single-tenant) so local `dev`/`start` runs seed
* demo data inline at boot; set `OS_MULTI_TENANT=true` for cloud /
* demo data inline at boot; set `OS_MULTI_ORG_ENABLED=true` for cloud /
* production multi-org deployments. Pass an explicit boolean to override
* (useful in tests).
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/objectql/src/sys-metadata-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ const RUNTIME_CREATE_ALLOWED_TYPES: ReadonlySet<string> = new Set(
let _envWritableMetadataTypes: Set<string> | null = null;
function envWritableMetadataTypes(): ReadonlySet<string> {
if (_envWritableMetadataTypes !== null) return _envWritableMetadataTypes;
const raw = readEnvWithDeprecation('OS_METADATA_WRITABLE', 'OBJECTSTACK_METADATA_WRITABLE') || '';
const raw = readEnvWithDeprecation('OS_METADATA_WRITABLE', []) || '';
const set = new Set<string>();
for (const tok of raw.split(',')) {
const t = tok.trim();
Expand Down
5 changes: 2 additions & 3 deletions packages/plugins/driver-sql/src/sql-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2222,7 +2222,7 @@ export class SqlDriver implements IDataDriver {

/**
* Whether the host kernel runs in multi-tenant mode — read once from
* `OS_MULTI_ORG_ENABLED` (or the deprecated `OS_MULTI_TENANT`), matching how
* `OS_MULTI_ORG_ENABLED`, matching how
* the SchemaRegistry / SecurityPlugin pick the mode. Used to gate the
* tenant-audit warning: it's only meaningful where tenant isolation is
* actually enforced (org-scoping installed).
Expand All @@ -2231,8 +2231,7 @@ export class SqlDriver implements IDataDriver {
protected isMultiTenantMode(): boolean {
if (this._multiTenantMode === undefined) {
// Single source of truth (shared with auth/registry/CLI) — previously
// this read `process.env` inline and so never emitted the
// `OS_MULTI_TENANT` deprecation warning the other sites do.
// this read `process.env` inline instead of the shared resolver.
this._multiTenantMode = resolveMultiOrgEnabled();
}
return this._multiTenantMode;
Expand Down
13 changes: 5 additions & 8 deletions packages/plugins/plugin-auth/src/auth-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ export class AuthManager {
...(() => {
const origins: string[] = [...(this.config.trustedOrigins || [])];
// Sync with OS_CORS_ORIGIN env var (comma-separated)
const corsOrigin = readEnvWithDeprecation('OS_CORS_ORIGIN', 'CORS_ORIGIN');
const corsOrigin = readEnvWithDeprecation('OS_CORS_ORIGIN', 'CORS_ORIGIN', { silent: true });
if (corsOrigin && corsOrigin !== '*') {
corsOrigin.split(',').map(s => s.trim()).filter(Boolean).forEach(o => {
if (!origins.includes(o)) origins.push(o);
Expand Down Expand Up @@ -1137,9 +1137,8 @@ export class AuthManager {
// The plugin itself is always installed (so list/update/invite endpoints
// keep responding); only the `create` operation is denied when the
// deployment is provisioned in single-org mode. Resolution order:
// 1. explicit `OS_MULTI_ORG_ENABLED` (wins for backwards compat),
// 2. else `OS_MULTI_TENANT` (multi-tenant deployments are always
// multi-org), default `'false'` → single-org / per-env runtime.
// `OS_MULTI_ORG_ENABLED` (default `'false'` → single-org /
// per-env runtime).
beforeCreateOrganization: async () => {
if (!resolveMultiOrgEnabled()) {
const { APIError } = await import('better-auth/api');
Expand Down Expand Up @@ -1565,7 +1564,7 @@ export class AuthManager {
* Generate a secure secret if not provided
*/
private generateSecret(): string {
const envSecret = readEnvWithDeprecation('OS_AUTH_SECRET', ['AUTH_SECRET', 'BETTER_AUTH_SECRET']);
const envSecret = readEnvWithDeprecation('OS_AUTH_SECRET', ['AUTH_SECRET', 'BETTER_AUTH_SECRET'], { silent: true });
if (envSecret) return envSecret;

// No secret configured. In production this is FATAL: a predictable
Expand Down Expand Up @@ -1863,9 +1862,7 @@ export class AuthManager {
// Extract enabled features
const pluginConfig: Partial<AuthPluginConfig> = this.config.plugins ?? {};
// Multi-org capability (UI org-switcher, "create org" action, etc.).
// Resolution order: explicit `OS_MULTI_ORG_ENABLED` wins, else fall
// back to legacy `OS_MULTI_TENANT` (multi-tenant deployments are always
// multi-org); default `'false'` → single-org / per-env runtime.
// `OS_MULTI_ORG_ENABLED` (default `'false'` → single-org / per-env runtime).
const multiOrgEnabled = resolveMultiOrgEnabled();

// Legal links shown beneath the login / register cards. Defaults to
Expand Down
8 changes: 4 additions & 4 deletions packages/plugins/plugin-hono-server/src/hono-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,14 @@ export class HonoServerPlugin implements Plugin {

// ─── CORS Middleware ──────────────────────────────────────────────────
// Enabled by default. Controlled via options.cors or environment variables.
const corsDisabledByEnv = readEnvWithDeprecation('OS_CORS_ENABLED', 'CORS_ENABLED') === 'false';
const corsDisabledByEnv = readEnvWithDeprecation('OS_CORS_ENABLED', 'CORS_ENABLED', { silent: true }) === 'false';
if (this.options.cors !== false && !corsDisabledByEnv) {
const corsOpts = typeof this.options.cors === 'object' ? this.options.cors : {};
const enabled = corsOpts.enabled ?? true;

if (enabled) {
let configuredOrigin: string | string[];
const corsOriginEnv = readEnvWithDeprecation('OS_CORS_ORIGIN', 'CORS_ORIGIN');
const corsOriginEnv = readEnvWithDeprecation('OS_CORS_ORIGIN', 'CORS_ORIGIN', { silent: true });
if (corsOpts.origins) {
configuredOrigin = corsOpts.origins;
} else if (corsOriginEnv) {
Expand All @@ -129,8 +129,8 @@ export class HonoServerPlugin implements Plugin {
configuredOrigin = '*';
}

const credentials = corsOpts.credentials ?? (readEnvWithDeprecation('OS_CORS_CREDENTIALS', 'CORS_CREDENTIALS') !== 'false');
const maxAgeEnv = readEnvWithDeprecation('OS_CORS_MAX_AGE', 'CORS_MAX_AGE');
const credentials = corsOpts.credentials ?? (readEnvWithDeprecation('OS_CORS_CREDENTIALS', 'CORS_CREDENTIALS', { silent: true }) !== 'false');
const maxAgeEnv = readEnvWithDeprecation('OS_CORS_MAX_AGE', 'CORS_MAX_AGE', { silent: true });
const maxAge = corsOpts.maxAge ?? (maxAgeEnv ? parseInt(maxAgeEnv, 10) : 86400);

// Determine origin handler based on configuration.
Expand Down
4 changes: 2 additions & 2 deletions packages/plugins/plugin-org-scoping/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ await kernel.use(new OrgScopingPlugin());
await kernel.use(new SecurityPlugin());
```

Or via the `OS_MULTI_TENANT` env switch when using `@objectstack/runtime` / `@objectstack/plugin-dev`:
Or via the `OS_MULTI_ORG_ENABLED` env switch when using `@objectstack/runtime` / `@objectstack/plugin-dev`:

```bash
OS_MULTI_TENANT=true objectstack serve
OS_MULTI_ORG_ENABLED=true objectstack serve
```

## Options
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-security/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ SecurityPlugin probes `getService('org-scoping')` at start time:

`organization_id` auto-injection on insert is provided by OrgScopingPlugin; `owner_id` auto-injection always runs in SecurityPlugin regardless.

In CLI / dev-server mode the `OS_MULTI_TENANT` environment variable (default `false`) toggles whether the runtime registers `OrgScopingPlugin` alongside `SecurityPlugin`. Set `OS_MULTI_TENANT=true` before `objectstack serve` / `pnpm dev` to enable.
In CLI / dev-server mode the `OS_MULTI_ORG_ENABLED` environment variable (default `false`) toggles whether the runtime registers `OrgScopingPlugin` alongside `SecurityPlugin`. Set `OS_MULTI_ORG_ENABLED=true` before `objectstack serve` / `pnpm dev` to enable.

## Key Exports

Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/standalone-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export async function createStandaloneStack(config?: StandaloneStackConfig): Pro
: resolvePath(cwd, artifactPathInput));

const dbUrl = cfg.databaseUrl
?? readEnvWithDeprecation('OS_DATABASE_URL', 'DATABASE_URL')?.trim()
?? readEnvWithDeprecation('OS_DATABASE_URL', 'DATABASE_URL', { silent: true })?.trim()
?? process.env.TURSO_DATABASE_URL?.trim()
?? (process.env.OS_HOME?.trim()
? `file:${resolvePath(resolveObjectStackHome(), 'data/standalone.db')}`
Expand Down
2 changes: 1 addition & 1 deletion packages/spec/src/api/protocol.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export const GetMetaTypesResponseSchema = lazySchema(() => z.object({
executionPinned: z.boolean().describe('Runtime transactions pin a specific historical version_hash (ADR-0009)'),
loadOrder: z.number().int().describe('Loading priority (lower = earlier)'),
domain: z.enum(['data', 'ui', 'automation', 'system', 'security', 'ai']).describe('Protocol domain'),
overrideSource: z.enum(['registry', 'env']).describe('Whether allowOrgOverride is set in the static registry or via OBJECTSTACK_METADATA_WRITABLE env var'),
overrideSource: z.enum(['registry', 'env']).describe('Whether allowOrgOverride is set in the static registry or via OS_METADATA_WRITABLE env var'),
createSeed: z.unknown().optional().describe('Authoritative minimal valid create seed for this type — Studio/CLI/API derive create defaults from it (single source of truth in @objectstack/spec). Absent for canvas-create types whose shape is built interactively.'),
})).optional().describe('Enriched per-type registry entries (Phase 3a)'),
}));
Expand Down
2 changes: 1 addition & 1 deletion packages/spec/src/data/object.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ const ObjectSchemaBase = z.object({
* declare them per-object (Salesforce-style). Currently injected:
*
* - `organization_id` — `lookup → sys_organization`. Injected only when
* the kernel runs in multi-tenant mode (`OS_MULTI_TENANT === 'true'`;
* the kernel runs in multi-tenant mode (`OS_MULTI_ORG_ENABLED === 'true'`;
* default is off — single-tenant).
* Required for the default `tenant_isolation` RLS policy and the
* SecurityPlugin's auto-fill on insert to take effect.
Expand Down
8 changes: 4 additions & 4 deletions packages/types/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ export function readEnvWithDeprecation(
* Resolve whether the deployment runs in multi-org (a.k.a. multi-tenant) mode.
*
* Single source of truth for the `OS_MULTI_ORG_ENABLED` flag. Resolution: the
* canonical `OS_MULTI_ORG_ENABLED` wins; else the deprecated `OS_MULTI_TENANT`
* (which fires the one-shot rename warning via {@link readEnvWithDeprecation});
* else `false`. Any value other than a case-insensitive `'false'` enables it.
* canonical `OS_MULTI_ORG_ENABLED`; else `false`. Any value other than a
* case-insensitive `'false'` enables it. (The legacy `OS_MULTI_TENANT` alias was
* removed in 11.0.)
*
* Every site that needs to know "is this multi-org?" — the SQL driver's
* tenant-audit gate, the auth manager's `/auth/config` feature flag and
Expand All @@ -99,7 +99,7 @@ export function readEnvWithDeprecation(
* result must be stable for the process lifetime.
*/
export function resolveMultiOrgEnabled(): boolean {
const raw = readEnvWithDeprecation('OS_MULTI_ORG_ENABLED', 'OS_MULTI_TENANT');
const raw = readEnvWithDeprecation('OS_MULTI_ORG_ENABLED', []);
return String(raw ?? 'false').toLowerCase() !== 'false';
}

Expand Down