mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-25 22:06:06 +00:00
docs(tenancy): record final isolation verification
This commit is contained in:
@@ -1,78 +1,126 @@
|
|||||||
# Multi-tenant verification report
|
# Multi-tenant isolation kernel verification report
|
||||||
|
|
||||||
Date: 2026-07-19
|
Date: 2026-07-20
|
||||||
|
|
||||||
This report records the implementation and verification evidence for the Core
|
Status: `CORE ISOLATION KERNEL VERIFIED — SAAS ACTIVATION REMAINS DISABLED`
|
||||||
Workspace isolation kernel. It does not claim that the closed SaaS Control
|
|
||||||
Plane or Cloud v2 deployment is production-ready.
|
This report records the final implementation and verification evidence for the
|
||||||
|
shared Workspace isolation kernel. It does not claim that the closed SaaS
|
||||||
|
Control Plane, billing system, or greenfield Cloud v2 deployment is
|
||||||
|
production-ready.
|
||||||
|
|
||||||
## Repository refs and scope
|
## Repository refs and scope
|
||||||
|
|
||||||
- LangBot branch: `feat/multi-tenants`
|
- LangBot branch: `feat/multi-tenants`
|
||||||
- LangBot implementation commit: `c6f826fe2d2e12cc73479155e8f25566048c1c56`
|
- LangBot implementation commit:
|
||||||
|
`a47bfe8167e12d1853667b3d059cfb940ff0643e`
|
||||||
- LangBot base: `origin/master` at
|
- LangBot base: `origin/master` at
|
||||||
`6baeb032a7f76c65337b51c7b58593de4687a61c`
|
`6baeb032a7f76c65337b51c7b58593de4687a61c`
|
||||||
- Plugin SDK branch: `feat/multi-tenants`
|
- Plugin SDK branch: `feat/multi-tenants`
|
||||||
- Plugin SDK commit and LangBot dependency pin:
|
- Plugin SDK commit and LangBot dependency pin:
|
||||||
`a1544b6b38a37ba72e3284f2836618144f0742c1`
|
`044536f3720a2a8424d92f78934b9623da9f7f1d`
|
||||||
- Space remained on `main` at
|
- Space remained on `main` at
|
||||||
`5058b039c2629e3b01ebd7358369b50e7df62a1c`, with no tracked changes made by
|
`5058b039c2629e3b01ebd7358369b50e7df62a1c`; this implementation made no
|
||||||
this implementation.
|
tracked Space change and does not reuse the legacy Space deployment model.
|
||||||
|
|
||||||
The SDK protocol is versioned as 0.4.15 but is intentionally consumed from the
|
The SDK protocol is versioned as 0.4.15 and is intentionally consumed from the
|
||||||
exact pushed Git commit on this feature branch. Publishing 0.4.15 and replacing
|
exact pushed Git commit on this feature branch. Publishing 0.4.15 and replacing
|
||||||
the Git pin with a registry pin remain merge-to-master release steps.
|
the Git pin with a registry pin remain merge-to-master release steps.
|
||||||
|
|
||||||
|
## Implemented boundaries
|
||||||
|
|
||||||
|
- OSS bootstraps exactly one Workspace per instance while allowing multiple
|
||||||
|
Accounts, memberships, invitations, and role-based permissions inside it.
|
||||||
|
Local configuration or an edition flag cannot activate SaaS multi-Workspace
|
||||||
|
routing.
|
||||||
|
- The Cloud bootstrap remains a closed-policy injection point. The closed entry
|
||||||
|
point owns Manifest signature verification; Core accepts only a verified
|
||||||
|
deployment receipt and validates its instance, expiry, generation,
|
||||||
|
capability, entitlement-adapter, and runtime-configuration invariants.
|
||||||
|
- PostgreSQL uses one shared business schema with application scope plus exact
|
||||||
|
`ENABLE` and `FORCE ROW LEVEL SECURITY` policies. Tenant UoWs keep `SET LOCAL`
|
||||||
|
and SQL on one transaction; transaction-free scopes avoid holding pool
|
||||||
|
connections across model or network waits.
|
||||||
|
- The one-shot release migrator owns DDL and grants a distinct runtime role only
|
||||||
|
business-table DML, `alembic_version` read access, required sequence access,
|
||||||
|
database `CONNECT`, and schema `USAGE`. Runtime startup reruns the complete
|
||||||
|
role, ACL, schema, session, extension, routine, parameter, and RLS audit.
|
||||||
|
- pgvector is stored in the same business database with Workspace-scoped keys,
|
||||||
|
checked dimensions, release-created partial ANN indexes, and RLS. Legacy
|
||||||
|
vector migration can temporarily suspend source-table RLS as a non-superuser,
|
||||||
|
non-`BYPASSRLS` table owner and restores each source table's prior state.
|
||||||
|
- One shared Plugin Runtime supervisor serves the logical instance. Every
|
||||||
|
enabled installation still owns one nsjail worker and one immutable
|
||||||
|
Workspace/installation/generation/revision/digest binding. Verified
|
||||||
|
same-digest code and dependency environments are read-only shared; processes
|
||||||
|
and writable state are never merged.
|
||||||
|
- One shared Box control plane admits at most one persistent logical `global`
|
||||||
|
sandbox for an entitled Workspace. Cloud admission is entitlement-gated,
|
||||||
|
generation-fenced, shared-volume-challenged, quota-aware, and fixed to
|
||||||
|
nsjail. Plain nsjail correctly fails Cloud readiness without a hard storage
|
||||||
|
quota provider.
|
||||||
|
- stdio MCP has an independent policy gate and is forced off by the Cloud
|
||||||
|
bootstrap even when Box is available.
|
||||||
|
|
||||||
## Automated verification
|
## Automated verification
|
||||||
|
|
||||||
### LangBot backend
|
### LangBot backend and static checks
|
||||||
|
|
||||||
```text
|
```text
|
||||||
.venv/bin/ruff check .
|
uv run pytest tests/unit_tests tests/integration -q
|
||||||
|
2374 passed, 30 skipped, 148 warnings in 26.76s
|
||||||
|
|
||||||
|
uv run ruff check .
|
||||||
passed
|
passed
|
||||||
|
|
||||||
changed and untracked Python format check
|
changed and untracked Python format check
|
||||||
220 files already formatted
|
112 files already formatted
|
||||||
|
|
||||||
.venv/bin/pytest tests/unit_tests -q
|
|
||||||
2051 passed, 1 skipped, 110 warnings in 26.87s
|
|
||||||
|
|
||||||
.venv/bin/pytest tests/integration -q
|
|
||||||
163 passed, 19 skipped, 28 warnings in 6.00s
|
|
||||||
|
|
||||||
uvx --from uv@latest uv lock --check
|
|
||||||
resolved 277 packages; passed
|
|
||||||
|
|
||||||
git diff --check
|
git diff --check
|
||||||
passed
|
passed
|
||||||
```
|
```
|
||||||
|
|
||||||
### Database migrations
|
The fresh SQLite journey test covers first-Account registration, automatic
|
||||||
|
singleton Workspace creation, authenticated current-Workspace bootstrap,
|
||||||
|
wizard-state persistence, and a 403 `edition_limit` response when an OSS owner
|
||||||
|
attempts to create a second Workspace.
|
||||||
|
|
||||||
- Fresh and upgraded SQLite schemas passed, including verified pre-boundary
|
### Real PostgreSQL 16 and pgvector
|
||||||
backups, forced-failure restore, retry, and revision manifest checks.
|
|
||||||
- A disposable PostgreSQL 16 instance exercised fresh and upgraded tenancy
|
|
||||||
migrations: `9 passed, 9 warnings in 8.57s`.
|
|
||||||
- Alembic has one head, `0010_scope_resources`, through the chain
|
|
||||||
`0008_mcp_resource_prefs -> 0009_workspace_tenancy ->
|
|
||||||
0010_scope_resources`.
|
|
||||||
- ORM-to-migration coverage found no missing tenant table.
|
|
||||||
|
|
||||||
### Plugin SDK and runtime
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
SDK full test suite
|
test_migrations_postgres.py
|
||||||
1029 passed, 18 warnings in 8.20s
|
test_pgvector_postgres.py
|
||||||
|
test_release_migration_postgres.py
|
||||||
SDK Ruff and action consistency checks
|
test_plugin_identity_migration.py
|
||||||
passed
|
20 passed, 11 warnings in 13.51s
|
||||||
|
|
||||||
Docker-backed Box integration tests
|
|
||||||
13 passed
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The SDK remote branch and LangBot Git dependency were both resolved and
|
The real-database suite covers fresh and upgraded RLS schemas, two-Workspace
|
||||||
verified at the same commit recorded above.
|
isolation, pgvector CRUD and ANN indexes, plugin installation identity,
|
||||||
|
operator/runtime credential separation, advisory locking, and actual Cloud
|
||||||
|
runtime initialization as the least-privilege role. Negative tests inject and
|
||||||
|
reject role membership, grant options, persistent GUCs, extra schemas and
|
||||||
|
relations, column ACLs, explicit routine and parameter ACLs, runtime-owned and
|
||||||
|
`SECURITY DEFINER` routines, extra/runtime-owned extensions, and foreign data
|
||||||
|
wrappers, servers, and user mappings.
|
||||||
|
|
||||||
|
Alembic has exactly one head: `0013_tenant_pgvector`.
|
||||||
|
|
||||||
|
### Plugin SDK and Box Runtime
|
||||||
|
|
||||||
|
```text
|
||||||
|
Plugin SDK full suite
|
||||||
|
1154 passed, 18 warnings in 11.99s
|
||||||
|
|
||||||
|
Plugin SDK Ruff and action consistency checks
|
||||||
|
passed
|
||||||
|
|
||||||
|
Docker-backed Box integration suite
|
||||||
|
18 passed, 33 warnings in 13.89s
|
||||||
|
```
|
||||||
|
|
||||||
|
The installed LangBot environment resolves `langbot-plugin==0.4.15` from the
|
||||||
|
same exact SDK commit recorded above.
|
||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
|
|
||||||
@@ -80,85 +128,78 @@ verified at the same commit recorded above.
|
|||||||
pnpm lint
|
pnpm lint
|
||||||
0 errors, 34 warnings
|
0 errors, 34 warnings
|
||||||
|
|
||||||
pnpm build
|
VITE_API_BASE_URL=/ pnpm build
|
||||||
passed; 3167 modules
|
passed; 3168 modules transformed
|
||||||
|
|
||||||
pnpm exec playwright test --project=chromium
|
pnpm exec playwright test --project=chromium
|
||||||
40 passed in 19.9s
|
40 passed in 21.9s
|
||||||
|
|
||||||
Bot form initialization race pressure test
|
|
||||||
10 passed in 9.4s
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The browser suite covers singleton bootstrap, explicit multi-Workspace
|
|
||||||
selection under the test policy, selector headers, account-state cleanup,
|
|
||||||
Workspace switching, and invitation terminal-state recovery.
|
|
||||||
|
|
||||||
### Packaging and deployment configuration
|
### Packaging and deployment configuration
|
||||||
|
|
||||||
- Docker Compose with Plugin Runtime and Box control tokens passed
|
- `uv lock --check` resolved 277 packages and passed.
|
||||||
`docker compose ... --profile all config --quiet`.
|
- The configuration template, migration workflow, and Docker Compose file
|
||||||
- Compose, Kubernetes, and the default configuration passed YAML parsing.
|
passed YAML parsing.
|
||||||
- Kubernetes selector, volume, and control-token reference checks passed.
|
- `docker compose -f docker/docker-compose.yaml config --quiet` passed with the
|
||||||
- `skills/bin/lbs index --check` and `skills/bin/lbs validate` passed.
|
existing warning that the top-level `version` field is obsolete.
|
||||||
- The staged secret scan found no committed credential; test-token literals
|
- Migration CI now runs the PostgreSQL RLS, pgvector, release-role, and plugin
|
||||||
were confirmed to be non-secret fixtures.
|
identity suites against PostgreSQL 16 with pgvector.
|
||||||
|
- The staged high-signal credential scan found no private key or provider token.
|
||||||
|
|
||||||
## Real browser E2E
|
## Real browser E2E
|
||||||
|
|
||||||
The bundled production web application and backend were started against a
|
A production frontend and backend were run against an isolated temporary
|
||||||
clean, temporary SQLite data directory. The following flows were performed in
|
SQLite data directory and operated in a real local Google Chrome Guest window:
|
||||||
the in-app browser against the real API rather than mocks:
|
|
||||||
|
|
||||||
1. The first Account registered and became owner of the automatically created
|
1. The first Account registered and received the automatically created default
|
||||||
singleton Workspace.
|
singleton Workspace as owner.
|
||||||
2. The owner created a one-time invitation link.
|
2. Skipping the setup wizard reached `/home/monitoring`; reload preserved the
|
||||||
3. A signed-out second Account inspected the fragment-based invitation,
|
dashboard and authenticated Workspace state.
|
||||||
registered, accepted it, and entered the Workspace as viewer.
|
3. Account Settings showed the OSS single-Workspace/multi-user model and owner
|
||||||
4. The same member was cycled through viewer, operator, developer, admin, and
|
membership. The owner created an invitation for another Account.
|
||||||
owner. Visible controls matched the backend permission matrix, including
|
4. After refresh, the pending invitation remained while its one-time raw secret
|
||||||
owner transfer only for an owner.
|
was no longer displayed.
|
||||||
5. A viewer invitation mutation returned 403 `permission_denied`; an owner
|
5. The related Workspace, user, system, invitation, and monitoring requests
|
||||||
attempt to create a second OSS Workspace returned 403 `edition_limit`.
|
returned successful responses in the backend log.
|
||||||
6. Owner/member account switching, refresh, and a newly opened browser tab
|
|
||||||
recovered only the active Account and singleton Workspace state.
|
|
||||||
7. Used, revoked, expired, and email-mismatched invitation states rendered
|
|
||||||
distinct errors. Terminal tokens were cleared, while the mismatch flow
|
|
||||||
allowed switching to the intended Account.
|
|
||||||
8. With the plugin system disabled, `/api/v1/plugins` and
|
|
||||||
`/api/v1/system/status/plugin-system` both returned 200 after a clean server
|
|
||||||
restart. The only remaining browser console error was the expected failure
|
|
||||||
to reach the optional external Space release feed.
|
|
||||||
|
|
||||||
The test server was stopped, port 15321 was verified free, and the temporary
|
The server was stopped, port 15321 was verified free, and the two temporary
|
||||||
test directory was moved to the system Trash. Multi-Workspace same-name and
|
test directories were moved to the system Trash for recoverable cleanup. No
|
||||||
same-identifier isolation is exercised by the automated test-only Cloud policy;
|
invitation secret is recorded in this report.
|
||||||
the OSS browser cannot create a second Workspace by design.
|
|
||||||
|
|
||||||
## Final audit
|
## Deliberately incomplete SaaS activation gates
|
||||||
|
|
||||||
The final isolation audit found no remaining P0/P1 tenant isolation issue in
|
|
||||||
the OSS singleton-Workspace, multi-user scope. Focused checks also covered the
|
|
||||||
plugin-disabled Workspace fence, invitation fragment/account-switch behavior,
|
|
||||||
and the Bot form initialization sequence. Unrelated untracked workspace files
|
|
||||||
were excluded from both commits.
|
|
||||||
|
|
||||||
## Deliberately incomplete SaaS gates
|
|
||||||
|
|
||||||
Multi-Workspace activation remains unavailable in the open-source bootstrap.
|
Multi-Workspace activation remains unavailable in the open-source bootstrap.
|
||||||
Cloud v2 must remain disabled until the following closed-system gates are
|
Cloud v2 must stay disabled until all of the following are implemented and
|
||||||
implemented and independently verified:
|
independently verified:
|
||||||
|
|
||||||
- signed `InstanceManifest` verification and closed `CloudWorkspacePolicy`
|
- the closed Control Plane owns the Account/Workspace directory, memberships,
|
||||||
injection;
|
invitations, monotonic execution generations, entitlements, usage,
|
||||||
- authoritative Account, Workspace, Membership, Invitation, placement,
|
subscriptions, and billing;
|
||||||
entitlement, usage, subscription, and billing services;
|
- ordinary tenant writes hold a generation-aware fence through commit, and a
|
||||||
- generation-aware database transaction and outbox fences through commit;
|
generation-stamped outbox or equivalent atomic publish fence protects
|
||||||
- stable durable object references across placement-generation cutovers;
|
external side effects;
|
||||||
- atomic shared OAuth-state and directory-projection stores;
|
- durable object references survive execution-generation cutovers without
|
||||||
- tenant-safe egress and SSRF enforcement for all configurable outbound URLs;
|
stranding files, images, plugin artifacts, or knowledge-base content;
|
||||||
- a greenfield Cloud v2 deployment, without carrying forward the legacy Space
|
- Plugin Runtime deployment provides delegated cgroup v2 plus tenant-safe
|
||||||
deployment model.
|
network isolation and egress policy. The current shared network namespace is
|
||||||
|
not an acceptable public-SaaS isolation boundary;
|
||||||
|
- Box deployment supplies and proves hard byte and inode quotas for Workspace,
|
||||||
|
Skill, root, home, and temporary storage; stock nsjail intentionally fails
|
||||||
|
this readiness contract;
|
||||||
|
- production PostgreSQL uses a dedicated cluster/endpoint, or a tested
|
||||||
|
HBA/proxy policy proves that the cluster-wide runtime credential can connect
|
||||||
|
only to the target business database;
|
||||||
|
- the release migrator is deployed as a one-shot Job with credential issuance,
|
||||||
|
backup, rollback, and orchestration retry procedures. A future direct
|
||||||
|
migrator/pooler runtime endpoint split additionally requires an immutable
|
||||||
|
database-backed cluster identity;
|
||||||
|
- OAuth exchange and directory projection state use an atomic shared store for
|
||||||
|
horizontally scaled replicas;
|
||||||
|
- Workspace release, data export, deletion, and single-Workspace restore
|
||||||
|
semantics are decided and implemented; and
|
||||||
|
- the greenfield Cloud v2 deployment is validated without inheriting the old
|
||||||
|
Space deployment topology.
|
||||||
|
|
||||||
These are release gates, not hidden fallbacks. Mutable Core configuration or an
|
These are explicit release gates, not hidden fallbacks. The current branch is a
|
||||||
edition flag cannot enable SaaS multi-tenancy.
|
verified isolation foundation on which the closed SaaS system can be built; it
|
||||||
|
is not a production Cloud activation.
|
||||||
|
|||||||
Reference in New Issue
Block a user