11 KiB
Cloud v2 multi-tenant verification report
Date: 2026-07-24
Status: FOUNDATION AND CONTROL PLANE VERIFIED — PRODUCTION ACTIVATION REMAINS GATED
This report records the implementation and verification evidence for one
logical LangBot instance serving multiple Workspace tenants. It covers the
open-source Core, the shared Plugin/Box runtimes, the closed Space adapter, and
the Space Cloud v2 modular-monolith control plane. It does not claim that the
production Cloud deployment may enable CLOUD_V2_ENABLED yet.
Repository refs and scope
- LangBot Core: branch
feat/multi-tenants - langbot-plugin-sdk: branch
feat/multi-tenants, commite7d946af4a6b1494fbe74627c1815ace19ac8991 - langbot-space: branch
feat/cloud-v2-control-plane - Closed Core adapter:
langbot-space/cloud-adapter - SDK protocol/package version:
0.4.18
Core pins the exact pushed SDK commit above. Cloud v2 is a greenfield multi-Workspace deployment and does not provision one Pod, database, queue, or Runtime per tenant. Legacy Space Pods remain a compatibility surface only.
Implemented product and security boundaries
Workspace and identity
- SaaS has one stable
instance_uuid;workspace_uuidis the tenant key. - Registering an Account creates its personal Workspace and owner Membership in the same PostgreSQL transaction.
- A Workspace may contain multiple users with owner, admin, and member roles. Invitation acceptance is token-hash based, email-bound, single-use, concurrency-safe, and member-limit checked while locked.
- Community remains exactly one local Workspace while supporting multiple Accounts and fixed RBAC roles. Installing the closed adapter is required to inject the SaaS policy; configuration alone cannot activate it.
- Disabled or deleted Accounts cannot use password/code/OAuth login, sessions, refresh/access tokens, or personal access tokens.
Closed Space control plane
CLOUD_V2_ENABLEDdefaults to false. Invalid or incomplete configuration fails startup, and disabled endpoints return 503.- Space owns Workspace/Membership/Invitation, versioned plans, subscriptions, entitlements, usage events, directory outbox, and signed instance manifests.
- Free and Pro are Workspace plans. Pro projects one managed global sandbox; Free projects none. Both force stdio MCP off in the first release.
- Subscription changes use Workspace advisory locking, expected entitlement revision, one-live-order uniqueness, idempotent usage ingestion, period-end downgrade, renewal, and lazy expiry settlement.
- Directory snapshot and per-Workspace delta reads use repeatable-read, read-only transactions with a high-water cursor. Core stores a replica-local consumer cursor and shared PostgreSQL projection state, snapshot coverage, and inbox rows.
- The
/cloudpage selects a Workspace and shows its independent subscription, entitlement, limits, and usage. When Cloud v2 is disabled or the backend does not expose the feature field, the complete legacy Welcome/Pod UI is used.
PostgreSQL and pgvector
- SaaS business data uses one PostgreSQL shared schema with application scope plus forced RLS. Cloud directory writes are separated from local tenant writes.
- Projected Account and Membership revisions are monotonic. Tombstones remove memberships, and stale revisions cannot resurrect them.
- pgvector shares the business PostgreSQL database and remains Workspace-scoped.
- Space runs versioned SQL migrations before application seeds. A fresh
database, a partial pre-migration database, and an existing-baseline path
converge without startup-time
AutoMigrate.
Shared Plugin Runtime
- One instance-scoped trusted Supervisor serves multiple Workspaces.
- Every enabled installation has its own nsjail process bound to
(instance, workspace, execution generation, installation, runtime revision, artifact digest). - Verified same-digest code and dependency files are mounted read-only and may be shared; home, tmp, data, process namespace, registration capability, and cgroup are private.
- Instance configuration owns CPU, memory, PID, open-file, and file-size limits. Plugin manifests cannot increase them.
- Memory includes swap: nsjail receives
memory.maxandswap.max=0. - Unexpected worker exit is recovered by a completion callback with bounded per-installation exponential backoff. Remove, reconcile, Runtime shutdown, and container SIGTERM perform a graceful-to-SIGKILL bounded reap.
Shared Box Runtime and MCP
- One shared Box control plane serves Workspace-bound logical sessions.
- Cloud grants allow at most one persistent
globalsession for an entitled Workspace and no managed processes in the first release. - Cloud is fixed to nsjail and network-off. Core and Box prove the shared durable Workspace mount with an authenticated marker challenge.
- stdio MCP is independently gated and forced off for Cloud v2.
- The current nsjail Box backend does not provide hard byte/inode quotas, so Cloud readiness correctly fails closed instead of silently using a soft directory scan.
Automated verification
LangBot Core
uv run --no-sync pytest -q
2590 passed, 32 skipped, 177 warnings
real PostgreSQL migration, pgvector, and release-migrator suites
21 passed, 11 warnings
uv run --no-sync ruff check .
uv lock --check
git diff --check
passed
The full suite ran without the closed adapter installed, proving the open-source single-Workspace/multi-user path remains standalone. Focused closed-adapter, directory projection, runtime connector, Box cleanup, and configuration suites also passed with the adapter installed.
Plugin SDK and real Linux runtime
SDK full suite
1226 passed, 22 existing warnings
Ruff check and format check
git diff --check
passed
A privileged Linux test container with host cgroup namespace ran one shared Runtime and two Workspace installations:
- both workers referenced the same artifact inode;
- home, tmp, and data inodes were distinct;
- each plugin saw only PID 1 in its private PID namespace;
- a tampered binding and an unknown installation were rejected;
- the control token was absent from worker environments;
- cgroups were distinct with
memory.max=134217728,memory.swap.max=0,pids.max=32, andcpu.max=500000 1000000; - touching 256 MiB exited with code 137 without swap growth;
- the 32nd fork failed with
EAGAIN; - reconcile and container SIGTERM removed the worker cgroups.
The same run started the Runtime from a non-root working directory, covering absolute nsjail mount-source normalization.
Space backend, adapter, and frontend
go test ./...
passed
fresh PostgreSQL app startup, partial-baseline migration,
Cloud v2 migration rerun, and control-plane integration
passed
closed adapter pytest and Ruff
passed
pnpm exec tsc --noEmit
targeted ESLint for changed Cloud files
pnpm build
passed; 47 routes generated
The PostgreSQL checks started from an empty database and verified all 32 registered migrations in order, Cloud v2 Free/Pro seeds, legacy plan seeds, Cloud columns/indexes, and repeatable reruns.
Cross-service and browser E2E
Signed Space-to-Core directory projection
Using an isolated Space PostgreSQL database and a migrated Core PostgreSQL database:
- Space issued a signed manifest for the fixed LangBot instance.
- Space returned a directory snapshot at cursor 5 containing two Workspaces, two projected Accounts, and owner/admin memberships.
- Core verified the signature, instance, release, validity window, and capability before injecting the Cloud Workspace policy.
- Core stored both Workspaces, all active Memberships, both projected Accounts, snapshot coverage, inbox entries, and cursor 5.
- Account-field-only projection revisions and Workspace directory revisions remained independent, and cross-Workspace Account conflicts failed closed.
Real browser Cloud v2 flow
A real local browser operated the Space frontend and backend:
- An owner logged in and saw the automatically created personal Workspace.
- A second Account invited the owner as admin to another Workspace.
- The selector showed both Workspaces and switched between them without stale subscription or usage state crossing the selection.
- Each Workspace showed its own Free entitlement and limits.
- Pro checkout opened for the selected Workspace. With no configured payment channels, confirmation was visibly disabled and no order was created.
- No browser errors occurred; only the pre-existing i18next warning for the
short
enlocale was observed.
The legacy feature-flag branch was then covered by API/static checks and the
production build: false or missing cloud_v2_enabled renders the original
Welcome/Pod client; a failed web-config request renders an explicit retry
instead of guessing a deployment mode.
Deliberate Core startup failure
Core completed signed manifest verification and directory projection, then stopped at the Box readiness gate because the current nsjail backend cannot prove hard Workspace byte/inode quota enforcement. Connector shutdown and reconnect tasks were cleanly reaped; no event-loop or never-awaited coroutine warning remained.
This is a successful fail-closed acceptance result, not a passing production Cloud boot.
Remaining production activation gates
Cloud v2 must remain disabled until these gates are closed:
- Box provides and proves hard byte and inode quotas for Workspace, Skill, root, home, and tmp storage.
- Plugin installation writable data receives an operator-owned hard total disk quota.
- Plugin and future networked Box workloads have tenant-safe egress/SSRF policy.
- Plugin Runtime adds jitter, global restart concurrency limiting, and a Runtime-level circuit breaker, then passes systemic-failure injection.
- M0 rolls Core and Plugin Runtime together until authenticated Runtime takeover or an owner lease/fencing protocol exists.
- Account registration moves New API provisioning to an in-process, transactional outbox reconciler keyed by Space Account UUID.
- EPay and Stripe fulfillment bind provider identity, amount, currency, channel/session, payment status, and expiry to the locked order transaction; provider transaction IDs are unique and replay-safe.
- Payment-session creation and stale
processingorders gain in-process reconciliation. - Cloud v2 subscription service periods are stored immutably and included in recognized-revenue reporting.
- Production migration Job, backup/rollback, PostgreSQL credential/network boundaries, horizontal-replica fault injection, Workspace release/export, deletion, and restore semantics are completed.
These gates intentionally add no tenant-specific service. They are implemented inside the existing Space, Core, Plugin Runtime, Box Runtime, and PostgreSQL components to preserve the architecture goal: near-zero static cost for a new Workspace.