@happyvertical/smrt-app-runtime
Reusable infrastructure composition for s-m-r-t applications. The package
implements the validated profiles from @happyvertical/smrt-config without
forking domain objects, generated surfaces, effects, approvals, or job calls.
Private local applications
import {
initializeLocalApplicationRuntime,
} from '@happyvertical/smrt-app-runtime';
const { runtime, bootstrap, diagnostics } =
await initializeLocalApplicationRuntime({
appId: 'my-app',
sourceRoot: process.cwd(),
prepareDatabase: runApplicationMigrations,
});
// Show bootstrap.token only in the loopback onboarding URL. The plaintext is
// returned once and only its HMAC is persisted.
Application migrations run through the explicit prepareDatabase hook while
the runtime holds the root lease. A rejected migration is normalized into a
LocalRuntimeError with the stable migration_failed code and the fixed,
secret-free message exported as MIGRATION_FAILED_MESSAGE: "The application
migration step failed; run pnpm app:setup and inspect the private migration
logs." The migration driver's own text — a likely carrier of a connection
string, credential, path, or environment value — is never surfaced; it is kept
as a non-enumerable cause for private logs. Startup releases the database
handle and the initialization lease, so the same application root and data
directory remain retryable once the migration is fixed.
The path resolver selects the current user's OS application-data directory
(~/Library/Application Support on macOS, %LOCALAPPDATA% on Windows, or
$XDG_DATA_HOME / ~/.local/share on Linux). Secure initialization currently
requires Node to expose nonzero O_NOFOLLOW and O_DIRECTORY filesystem
flags; it fails closed on platforms without that custody support and therefore
does not yet claim Windows runtime support. The data root contains a mode-0600
SQLite database, a user-owned asset directory, and generated mode-0600
application secret material. The root must be a dedicated application
directory: placing it inside the source checkout or choosing an ancestor is
refused, as is choosing the user home itself or the filesystem root. An
explicitly configured root that already exists must already be
owned by the current user with mode 0700; initialization rejects it without
changing permissions or creating artifacts when that custody proof fails.
An empty root is claimed with an app-specific, empty mode-0600 marker. A
populated root is accepted only with that valid marker, so selecting an
unrelated private directory fails without changing its contents. A pending
marker makes the claim crash-recoverable while the database is first acquired;
it is promoted atomically after the released SQL custody boundary verifies the
complete ancestor/root chain, including macOS ACLs. Failed custody removes the
pending claim and every directory only when they were created by that attempt;
an inherited pending claim and its database remain authoritative for retry.
Initializers for the same data root are serialized across processes by an
exclusive transaction in a dedicated SQLite lock database under a private
per-user, root-keyed custody directory. The released @happyvertical/sql
trusted-parent boundary validates that directory, its ancestors, and the lock
leaf for ownership, write permissions, static links, and macOS ACLs before it
opens the lock database. Atomic SQLite locking elects one owner without deleting
or replacing a pathname, so there is no stale-file ABA window and no PID or
clock lease. Process or worker death releases the kernel lock automatically. A
read-only path, custody, and ownership-marker preflight rejects an obviously
invalid application root before creating its lock-registry entry; the complete
checks run again under the lease before application-root mutation. The lease
covers storage acquisition, secret publication, SQLite tuning,
application migrations, and bootstrap construction. Contenders wait up to two
minutes for that complete sequence, so normal migrations can finish without
overlap. Marker or temporary-secret cleanup therefore cannot race another
active initializer.
Every existing path component is opened without following symbolic links and
checked against its canonical path before descendants or secret bytes are
written. The runtime then acquires SQLite through @happyvertical/sql's
explicit node:sqlite trusted-parent custody boundary, rooted at the mode-0700
application data directory. Unsupported runtimes or platforms and unsafe
ownership, permissions, ACLs, or path components fail closed.
The application secret is published by atomically linking a fully written, synced mode-0600 temporary file into place. Concurrent installers validate and reuse the one winning value; an incomplete or malformed existing secret is rejected rather than overwritten, and stale interrupted temporary files are removed after a complete value is durably available.
The custody boundary prevents static link traversal and mutation by other OS principals while the application retains control of that directory. Hostile code already running as the same user is outside this boundary; isolating same-account processes requires an OS sandbox and a descriptor-relative SQLite VFS.
Owner onboarding binds to 127.0.0.1 by default and accepts only loopback IP
literals (127.0.0.0/8 or ::1), avoiding hostname-resolution ambiguity.
The first valid claim creates a real global Person, User, default Tenant,
owner Role / Membership, and server-side Session atomically. Startup and
setup are idempotent; replayed, expired, or concurrent claims fail closed.
Authenticated session TTLs are configured in whole seconds with a minimum of
one second, and invalid values are rejected before filesystem mutation.
Background jobs and application-defined paid capabilities remain disabled until
explicitly enabled. With embedded job topology, createEmbeddedJobRunner()
returns the normal s-m-r-t TaskRunner, so the application keeps one enqueue
and execution contract without requiring a separate worker service.
Self-hosted and cloud applications
The deployed initializer validates the selected profile against concrete, provider-owned bindings. Database URLs, OIDC credentials, storage keys, and secret-manager identifiers stay inside those adapters and are never copied into the runtime snapshot.
import {
initializeDeployedApplicationRuntime,
} from '@happyvertical/smrt-app-runtime';
import { getDatabase } from '@happyvertical/sql';
const initialized = await initializeDeployedApplicationRuntime({
profile: 'self-hosted',
database: {
engine: 'postgres',
connect: () => getDatabase({
type: 'postgres',
url: requirePrivateSetting('DATABASE_URL'),
}),
close: async (db) => {
await db.close?.();
},
},
authentication: {
provider: 'oidc',
readiness: () => oidcProvider.assertReady(),
},
assets: {
provider: 's3-compatible',
readiness: () => assetProvider.assertReady(),
},
secrets: {
provider: 'environment',
readiness: () => secretProvider.assertReady(),
},
prepareDatabase: runApplicationMigrations,
});
Startup validates every binding, including the provider-owned database cleanup
boundary, before opening a connection. A missing public-auth or secret binding,
a selector mismatch, an unavailable provider, a failed
PostgreSQL probe, or a failed migration rejects startup. Provider failures are
reported with stable component codes and omit the underlying provider message
so credentials cannot leak into HTTP or orchestration payloads.
A custom database readiness callback is additive; the runtime always runs its
own PostgreSQL-specific server-version probe before startup or readiness can
succeed.
If cleanup after a startup failure also fails,
DeployedRuntimeCleanupError.retryCleanup() retains the redacted, idempotent
ownership path until the provider closes successfully.
health() reports process liveness. readiness() rechecks PostgreSQL,
authentication, assets, and secrets and returns only ready / not-ready
component states. diagnostics() reports the resolved profile, explicit
provider selectors, tenancy posture, and worker topology without secret values.
Job producers keep using SmrtObject.bg() / background().enqueue() in every
profile. Deployed worker entry points initialize the ordinary runners against
the same PostgreSQL database:
const taskWorker = await initialized.createTaskWorker({ concurrency: 8 });
await taskWorker.start();
// Run this in a separate schedule-worker process, not beside the web server.
const scheduleWorker = await initialized.createScheduleWorker();
await scheduleWorker.start();
initialized.close() drains in-flight readiness/session/worker initialization
and serialized runner start/stop operations, stops every runner returned by
this runtime, and only then closes PostgreSQL.
Callers may stop a runner earlier, but must not restart a returned runner after
its runtime has begun shutdown; lifecycle-gated start() calls then reject.
An awaited close() request made from inside a tracked provider or worker
operation acknowledges shutdown so that operation can unwind; callers outside
the operation continue to await complete worker and database cleanup.
Self-hosted deployments may select OIDC or magic-link authentication, explicit single- or multi-tenancy, local or S3-compatible assets, and environment, local-file, or external secrets. Cloud requires hosted identity, PostgreSQL, multi-tenancy with required tenant context, managed/external secrets, managed/S3-compatible object storage, public TLS, and scalable workers. Cloud may select application isolation instead of PostgreSQL RLS, but it can never enable an unscoped/root-tenant fallback.
The application runtime does not provision databases, buckets, identity
providers, secret managers, worker fleets, TLS, billing, or a hosted control
plane. Those remain operator/managed-platform responsibilities. Enabling the
database-rls selector also requires the deployment migration to apply the
documented s-m-r-t PostgreSQL policies; the selector does not grant or mutate
database privileges at runtime.