Skip to content

[AIG-649] Multi-tenancy + workspace isolation (M3-21)#37

Merged
blackms merged 7 commits into
mainfrom
pm-agent/aig-649-multi-tenancy
May 29, 2026
Merged

[AIG-649] Multi-tenancy + workspace isolation (M3-21)#37
blackms merged 7 commits into
mainfrom
pm-agent/aig-649-multi-tenancy

Conversation

@blackms
Copy link
Copy Markdown
Owner

@blackms blackms commented May 28, 2026

Implements AIG-649.

Milestone: M3 - Enterprise
Estimate: 8 pts

Summary

See commit message for full implementation details (schema, API, CLI, tests, docs).

Notes from PM agent

MIGRATION: 008. FEATURE FLAG: implemented as default OFF (decision deferred to human: v2.0 breaking vs v1.x feature-flag). Retrofit strategy in docs/MULTITENANT.md.


Auto-opened by aistack PM agent on 2026-05-28 10:22. Review with /review or human dispatch.

Summary by CodeRabbit

  • New Features
    • Multi-tenant & workspace isolation with RBAC (tenant_admin, workspace_admin, member), tenant-scoped memory and cache partitioning, tenant-aware agent execution, tenant management REST API, CLI tenant commands, tenant switcher UI, and single→multi migration tooling.
  • Documentation
    • Comprehensive multi-tenancy guide: concepts, context resolution, migration checklist, retrofit SQL templates, and CLI usage.
  • Tests
    • New unit/integration suites covering migration, service, routes, memory scoping, and tenant UI behavior.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Warning

Review limit reached

@blackms, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 15 minutes and 26 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4f2881cc-35b4-4dc1-bf47-12409539d01f

📥 Commits

Reviewing files that changed from the base of the PR and between 14d8e2f and fdf1002.

📒 Files selected for processing (2)
  • src/web/routes/index.ts
  • src/web/server.ts
📝 Walkthrough

Walkthrough

Adds a full multi-tenancy implementation: DB migration and TenantService, request-context resolution with AsyncLocalStorage, tenant-aware memory namespacing and audit stamping, REST routes and CLI tooling, agent/dispatcher tenant propagation, frontend tenant switcher, migration tooling, tests, and documentation.

Changes

Multi-Tenancy Foundation

Layer / File(s) Summary
Database schema and TenantService
migrations/008_multitenant.sql, src/multitenancy/service.ts
Creates tenants, workspaces, tenant_users with indexes and COALESCE-based uniqueness; adds TenantService with tenant/workspace/membership CRUD and RBAC resolution.
Multitenancy module & migration API
src/multitenancy/index.ts, tests/integration/multitenancy-migration.test.ts
Exports MultitenancyContext/config types, workspaceNamespace, withTenantContext/getActiveTenantContext/runWithTenantContext, and migrateSingleToMulti with idempotent default tenant/workspace provisioning and optional membership backfill.
Config & types
src/types.ts, src/utils/config.ts, src/auth/types.ts
Adds MultitenancyConfig to AgentStackConfig and config schema; extends AuthContext with optional tenantId, workspaceId, tenantRole.
Memory layer & audit
src/memory/index.ts, src/memory/sqlite-store.ts, tests/unit/memory-agent-scoping.test.ts
Applies tenant-prefixed namespaces across store/get/list/search/delete; denies cross-tenant getById access; enriches agent-audit metadata with tenant/workspace.
Agent spawner & dispatcher
src/agents/spawner.ts, src/tasks/smart-dispatcher.ts
SpawnOptions accepts tenantContext; spawner prefixes memoryNamespace and augments metadata with tenant ids. SmartDispatcher accepts tenantContext and partitions cache keys/writes and logs by tenant scope.
REST API routes
src/web/routes/tenants.ts, src/web/routes/index.ts, src/web/server.ts, tests/unit/multitenancy/tenant-routes.test.ts
Adds /api/v1/tenants* with lazy DB init, assertTenantRole enforcing RBAC (platform admin bypass), tenant/workspace CRUD, workspace listing inside runWithTenantContext, and route tests validating auth and cross-tenant checks.
CLI tenant management
src/cli/commands/tenant.ts, src/cli/commands/index.ts, src/cli/index.ts
New tenant CLI group: create, list, show, delete, migrate (single→multi), each opening/closing DB and reporting results.
Frontend TenantSwitcher
web/src/pages/TenantSwitcher.tsx
React page storing tenant/workspace slugs in localStorage, fetching tenants/workspaces, auto-select fallback behavior, emits aistack:tenant-changed on workspace change, and shows loading/error/empty states.
Docs & migration guidance
docs/MULTITENANT.md
Comprehensive documentation: entities, RBAC, feature-flag resolution order, CLI/REST/UI usage, memory namespacing, retrofit checklist with SQL template, post-merge ordering, and guardrails.

Sequence Diagram

sequenceDiagram
  participant Client
  participant Route
  participant withTenantContext
  participant TenantService
  participant AsyncLocalStorage
  Client->>Route: HTTP request (headers / JWT)
  Route->>withTenantContext: withTenantContext(req)
  withTenantContext->>TenantService: verify tenant/workspace/membership
  TenantService-->>withTenantContext: tenant/workspace/context
  withTenantContext->>AsyncLocalStorage: store context
  AsyncLocalStorage-->>Route: getActiveTenantContext() used by handlers
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • blackms/aistack#20: Related changes to SmartDispatcher caching and cache-key logic; this PR makes dispatch/cache tenant-aware.
  • blackms/aistack#17: Related memory/agent namespacing work; both PRs adjust memory namespace isolation mechanisms.
  • blackms/aistack#10: Related agent identity and audit wiring; this PR stamps tenant/workspace into audit metadata.

"I nibble keys and hop through code,
Tenants, workspaces neatly stowed.
Namespaces scoped, events that chime,
A rabbit cheers: isolated by design! 🐇"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly references the main feature (AIG-649 Multi-tenancy + workspace isolation) and includes the ticket identifier and milestone, accurately summarizing the primary change in the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pm-agent/aig-649-multi-tenancy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Owner Author

@blackms blackms left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES

The PR lands a clean schema + service + CLI + UI skeleton, with good tests for the isolated TenantService. However it falls substantially short of the AIG-649 acceptance criteria for tenancy isolation: most of the surface area is scaffolding that is never wired up, several routes are unauthenticated, and the migration violates the repo's own rollback convention. None of the critical isolation invariants in the review brief are actually enforced by code today.

I'm fine with the "base-layer + retrofit-deferred" framing documented in docs/MULTITENANT.md, but a few of the findings below are not retrofit work — they are gaps in what this PR itself promises to ship.


Blocking findings

1. Tenant REST routes have no authentication and no RBAC — anyone can list/create/delete tenants.
src/web/routes/tenants.ts registers POST /api/v1/tenants, DELETE /api/v1/tenants/:id, POST /api/v1/tenants/:id/workspaces, etc. and never calls createAuthMiddleware. Compare with src/web/routes/auth.ts:214/234/268, where admin-only routes explicitly wrap with createAuthMiddleware({ required: true, roles: ['admin'] }). As written, an unauthenticated attacker can:

  • enumerate every tenant (GET /api/v1/tenants)
  • create rogue tenants and workspaces
  • delete any tenant by ID via DELETE /api/v1/tenants/:id, which cascades to all workspaces + memberships

The PR description claims "RBAC default-deny"; current behavior is "no auth at all". At minimum every mutation route needs createAuthMiddleware({ required: true, roles: ['admin'] }), list routes need required: true, and the handlers must call TenantService.assertAccess(...) for tenant-scoped reads. Worth adding a regression test in tests/unit/web/routes/ that asserts 401 without a token and 403 for non-admins.

2. withTenantContext and workspaceNamespace are exported but never imported anywhere.
Grep over src/ confirms zero callers. There is no middleware that resolves a MultitenancyContext from the request, no place where AuthContext.tenantId/workspaceId/tenantRole is ever populated (despite the new fields in src/auth/types.ts), and no domain service that namespaces memory keys by workspaceNamespace(ctx). This means the "feature flag default OFF preserves single-tenant behavior" claim is trivially true — because the flag has no consumers. When a user flips multitenancy.enabled: true, nothing changes: there is no cross-tenant data leakage prevention because there is also no scoping at all.

That's defensible for memory-store / agents / tasks (called out as retrofit work). It is not defensible for the new HTTP and CLI surface this PR ships — those should wire withTenantContext end-to-end so the contract proven by service.test.ts ("tenant isolation invariant") actually applies to a real request path. Right now, even the new /api/v1/tenants/:id/workspaces route lets any caller list workspaces of any tenant regardless of membership.

3. Migration 008 is not reversible — violates migrations/README.md.
The repo README mandates a -- ==================== DOWN ==================== section with DROP statements, and migrations 001/002/003 all follow this. migrations/008_multitenant.sql only has UP. The review brief explicitly calls out "retrofit migration is reversible" as a hard criterion. Please append:

-- ==================== DOWN ====================
DROP INDEX IF EXISTS idx_tenant_users_workspace;
DROP INDEX IF EXISTS idx_tenant_users_tenant;
DROP INDEX IF EXISTS idx_tenant_users_user;
DROP TABLE IF EXISTS tenant_users;
DROP INDEX IF EXISTS idx_workspaces_tenant;
DROP TABLE IF EXISTS workspaces;
DROP INDEX IF EXISTS idx_tenants_slug;
DROP TABLE IF EXISTS tenants;

4. tenant_users PRIMARY KEY does not protect against duplicate tenant-wide memberships.
PRIMARY KEY (tenant_id, user_id, workspace_id) — in SQLite, two rows with workspace_id IS NULL for the same (tenant_id, user_id) do not collide because NULL is not equal to NULL inside a PK. Combined with the INSERT OR REPLACE in TenantService.addMembership, a tenant-wide grant for the same user can silently accumulate duplicates, and resolveRole will then double-count. Reproducer would be straightforward. Fix options:

  • replace the PK with a composite that uses COALESCE(workspace_id, ''), expressed via a UNIQUE INDEX on (tenant_id, user_id, COALESCE(workspace_id, '')), or
  • use two tables (tenant-wide vs workspace-scoped) and union at read time.

A test for "calling addMembership twice with the same (tenantId, userId, undefined) results in exactly one row" would lock this down.

Significant concerns

5. cachedDb singleton in src/web/routes/tenants.ts re-opens the DB outside the existing MemoryManager lifecycle. WebServer.constructor already does memoryManager.getStore().getDatabase(). Opening a second better-sqlite3 handle to the same file from getDb(config) risks lock contention and bypasses any pragmas the memory manager sets. Pass the DB handle into registerTenantRoutes(router, config, db) like the other routes do, and drop the cachedDb / _resetTenantRoutesDb plumbing.

6. ensureSchema() in TenantService silently runs DDL on every instantiation. The routes file constructs new TenantService(getDb(config)) on every request, which means we issue 9 CREATE TABLE IF NOT EXISTS / CREATE INDEX IF NOT EXISTS statements per call. Functionally fine, but wasteful and surprising under load. Either construct the service once at server startup, or gate ensureSchema behind a Database symbol marker so it runs once per handle.

7. Migration numbering jump to 008 is fragile. Documented as intentional, but please file a follow-up Linear ticket pinned to merge-time renumbering so it isn't forgotten. The migrate CLI command also doesn't look at the migrations/ SQL file at all — it just calls migrateSingleToMulti(db). Worth verifying the file gets applied through whatever the project's existing migration runner is (I couldn't find one referenced from WebServer.constructor).

8. TenantContextRequest.jwtClaims is read but never produced. src/web/middleware/auth.ts issues JWTs via AuthService whose payload (src/auth/types.ts JWTPayload) has no tenant_slug / workspace_slug fields. Either extend JWTPayload and the issuance path, or drop the jwtClaims branch from withTenantContext to avoid pretending it works.

Minor / nits

  • assertValidSlug should be applied to workspace slugs in migrateSingleToMulti callers' inputs too — currently migrateSingleToMulti({ tenantSlug: 'BadSlug!' }) surfaces the same error from createTenant, which is fine, but invalid slugs supplied via --tenant-slug on the CLI surface today as a generic Migration failed: Invalid tenant slug rather than a usage error.
  • TenantSwitcher.tsx persists selection to localStorage but the inline fetchJson helper never reads it back into request headers. So the dropdown today is purely cosmetic — no request actually carries X-Tenant-Slug. Either wire the active selection into a global fetch wrapper, or call this out as TODO.
  • docs/MULTITENANT.md "Application-layer guardrail" snippet uses authContext.tenantId — please add an inline comment referencing the new field added in src/auth/types.ts so reviewers can trace it.
  • idx_tenant_users_workspace will index a lot of NULLs given the tenant-wide membership pattern; not a correctness issue, just noting.

Test coverage gaps

  • Unit tests cover TenantService in isolation thoroughly — good.
  • Integration test covers the migration tool — good.
  • Missing: any test of withTenantContext (header to context resolution, JWT fallback, default fallback, denied access when user lacks membership).
  • Missing: any HTTP-level test of the new routes (auth, RBAC, cross-tenant access denial).
  • Missing: a regression asserting the "feature flag OFF = behavior identical to main" invariant. A simple integration test that boots the server with multitenancy.enabled: false and confirms a baseline request shape is unchanged would catch silent regressions when the retrofit lands.

Suggested path to merge

  1. Add DOWN section to migration 008 (blocking).
  2. Wrap all tenant routes in createAuthMiddleware({ required: true }) and admin role gating for mutations (blocking).
  3. Fix the tenant_users NULL-in-PK issue (blocking).
  4. Land the simplest possible withTenantContext HTTP middleware that populates AuthContext.tenantId/workspaceId/tenantRole for the new routes only, and have those routes call TenantService.assertAccess (blocking — without this, the PR doesn't materially deliver "workspace isolation").
  5. Share the DB handle with the rest of the web server (significant).
  6. Add the missing HTTP-level / context tests (significant).
  7. Retrofit checklist in docs/MULTITENANT.md is fine as a follow-up.

Happy to re-review once the blocking items are addressed.

aistack-pm-agent and others added 4 commits May 28, 2026 11:35
… safety

Replace module-scope ref with AsyncLocalStorage so the active tenant
context survives await boundaries WITHOUT leaking across interleaved
concurrent async handlers in the same process. The prior implementation
had a latent cross-tenant data-leak: while request A was awaiting I/O
inside runWithTenantContext(ctxA, ...), request B could enter
runWithTenantContext(ctxB, ...) and overwrite the module-scope ref;
on resumption, A's downstream calls (MemoryManager.search,
SmartDispatcher.dispatch, audit logs) would observe ctxB.

- runWithTenantContext now accepts () => T | Promise<T> and delegates
  to AsyncLocalStorage.run. Undefined ctx remains a pass-through to
  preserve single-tenant no-op semantics.
- getActiveTenantContext reads from the async store.
- _resetActiveTenantContext is now a documented no-op (storage scopes
  itself to the run() callback; nothing to reset).
- Add concurrency regression test: two interleaved async runs with
  awaits assert each only ever observes its own tenant id.
…i-tenancy

# Conflicts:
#	src/agents/spawner.ts
#	src/cli/commands/index.ts
#	src/cli/index.ts
#	src/types.ts
#	src/utils/config.ts
#	src/web/server.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
docs/MULTITENANT.md (1)

89-98: ⚡ Quick win

Clarify the ctx parameter and memory migration implications.

The code example shows workspaceNamespace(ctx) but doesn't explain what ctx is, its type (AuthContext? MultitenancyContext?), or how to obtain it in different contexts (e.g., request handlers, background jobs, CLI commands). Developers retrofitting existing code may be uncertain how to properly construct or pass this context.

Additionally, the note that "the migration tool does not rewrite them" (line 97) has important data isolation implications that should be stated explicitly:

  • Do pre-migration memory entries remain accessible after migration?
  • Are they shared across tenants or isolated in some way?
  • Is manual migration or cleanup required?

Without clarity here, implementations may inadvertently create data isolation vulnerabilities.

📝 Suggested improvements

After line 93, add:

The `ctx` parameter should be a `MultitenancyContext` object (typically extracted from `AuthContext`):

```ts
import { getActiveTenantContext } from '../multitenancy/index.js';

// In request handlers
const ctx = getActiveTenantContext(); // reads from AsyncLocalStorage

// In background jobs or CLI, set context explicitly
runWithTenantContext({ tenantId, workspaceId }, async () => {
  const ctx = getActiveTenantContext();
  const ns = workspaceNamespace(ctx);
  // ... memory operations
});

After line 98, add:

```markdown
**Pre-migration data handling:** Memory entries created before migration remain in their original (unscoped) namespace and are NOT automatically migrated. After enabling multi-tenancy:
- These entries become inaccessible via tenant-scoped queries
- Manual migration or cleanup is required if you need to preserve this data
- See "Retrofit checklist" below for the data backfill plan
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/MULTITENANT.md` around lines 89 - 98, The example using
workspaceNamespace(ctx) omits what ctx is and leaves data-migration implications
ambiguous; update the docs to state that ctx is a MultitenancyContext (often
derived from AuthContext via getActiveTenantContext) and show how to obtain it
in different runtimes (e.g., read from AsyncLocalStorage in request handlers or
wrap background/CLI work with runWithTenantContext to set tenantId/workspaceId),
and explicitly document that pre-migration memory entries remain in their
original unscoped namespace, become inaccessible to tenant-scoped queries after
migration, and therefore require manual migration or cleanup (refer to the
Retrofit checklist for backfill steps).
src/multitenancy/index.ts (1)

107-109: 💤 Low value

Missing workspace resolution silently falls back to tenant-only context.

When workspaceSlug is provided (via header, JWT, or config default) but the workspace doesn't exist in the database, the function continues with workspaceId: undefined rather than throwing. This could lead to unexpected behavior where a request targeting a specific workspace silently operates at tenant scope instead.

Consider throwing when an explicitly requested workspace is not found to surface configuration or migration issues early.

Suggested change
   const workspace = workspaceSlug
     ? service.getWorkspaceBySlug(tenant.id, workspaceSlug)
     : undefined;
+
+  if (workspaceSlug && !workspace) {
+    throw new Error(
+      `Workspace "${workspaceSlug}" not found in tenant "${tenantSlug}"`,
+    );
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/multitenancy/index.ts` around lines 107 - 109, When workspaceSlug is
present but service.getWorkspaceBySlug(tenant.id, workspaceSlug) returns
undefined, do not silently continue with workspaceId undefined; instead detect
that case and throw a clear error. Update the code around the workspace variable
(where workspaceSlug and service.getWorkspaceBySlug are used in
src/multitenancy/index.ts) to check if workspaceSlug && !workspace and throw a
descriptive error (e.g., "Workspace not found for slug: {workspaceSlug}") or a
suitable NotFound error so callers cannot silently fall back to tenant-only
context.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/web/routes/tenants.ts`:
- Around line 281-303: withTenantContext may produce a tenant context from
headers/JWT that doesn't match the route tenant id (id); before calling
runWithTenantContext you must enforce consistency by checking the resolved
context's tenant identifier against the path tenant (id) and if they differ,
discard or override the resolved context. Update the block around
withTenantContext/runWithTenantContext to compare resolved.tenantId (or
resolved.id/the appropriate tenant identifier on the resolved object) to id and
set resolved = undefined (or construct a context for the path-tenant) when they
don't match so downstream calls like service.listWorkspaces(id) always run under
the path tenant.

In `@web/src/pages/TenantSwitcher.tsx`:
- Around line 39-45: The fetchJson<T> helper currently returns the raw JSON
which in our tenant routes is wrapped as { success, data }, so callers (e.g.,
the TenantSwitcher code that expects tenants/workspaces) receive the envelope
instead of the actual payload; update fetchJson to detect and unwrap that
envelope: after parsing res.json(), if the object has a "data" property (and
optionally "success"), return obj.data as T, otherwise return the parsed object
as T so existing non-wrapped endpoints keep working; ensure the generic
signature of fetchJson<T> remains and that callers (the code that reads
tenants/workspaces) will now receive the unwrapped payload.
- Around line 73-76: Persisted tenantSlug may point to a removed tenant and
causes early return; after fetching tenants (data.tenants) reconcile the
current/persisted tenantSlug against the fetched list and if it is missing and
data.tenants.length > 0 call setTenantSlug(data.tenants[0].slug) and
persistActive(data.tenants[0].slug) to fall back to the first available tenant;
update the early-return logic that checks tenantSlug (in TenantSwitcher) so it
validates presence in data.tenants rather than only checking for a truthy slug.

---

Nitpick comments:
In `@docs/MULTITENANT.md`:
- Around line 89-98: The example using workspaceNamespace(ctx) omits what ctx is
and leaves data-migration implications ambiguous; update the docs to state that
ctx is a MultitenancyContext (often derived from AuthContext via
getActiveTenantContext) and show how to obtain it in different runtimes (e.g.,
read from AsyncLocalStorage in request handlers or wrap background/CLI work with
runWithTenantContext to set tenantId/workspaceId), and explicitly document that
pre-migration memory entries remain in their original unscoped namespace, become
inaccessible to tenant-scoped queries after migration, and therefore require
manual migration or cleanup (refer to the Retrofit checklist for backfill
steps).

In `@src/multitenancy/index.ts`:
- Around line 107-109: When workspaceSlug is present but
service.getWorkspaceBySlug(tenant.id, workspaceSlug) returns undefined, do not
silently continue with workspaceId undefined; instead detect that case and throw
a clear error. Update the code around the workspace variable (where
workspaceSlug and service.getWorkspaceBySlug are used in
src/multitenancy/index.ts) to check if workspaceSlug && !workspace and throw a
descriptive error (e.g., "Workspace not found for slug: {workspaceSlug}") or a
suitable NotFound error so callers cannot silently fall back to tenant-only
context.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 018f2ae3-e421-4ba2-85b2-3d10e4c5487f

📥 Commits

Reviewing files that changed from the base of the PR and between d1b64a2 and b683b39.

📒 Files selected for processing (22)
  • docs/MULTITENANT.md
  • migrations/008_multitenant.sql
  • src/agents/spawner.ts
  • src/auth/types.ts
  • src/cli/commands/index.ts
  • src/cli/commands/tenant.ts
  • src/cli/index.ts
  • src/memory/index.ts
  • src/memory/sqlite-store.ts
  • src/multitenancy/index.ts
  • src/multitenancy/service.ts
  • src/tasks/smart-dispatcher.ts
  • src/types.ts
  • src/utils/config.ts
  • src/web/routes/index.ts
  • src/web/routes/tenants.ts
  • src/web/server.ts
  • tests/integration/multitenancy-migration.test.ts
  • tests/unit/memory-agent-scoping.test.ts
  • tests/unit/multitenancy/service.test.ts
  • tests/unit/multitenancy/tenant-routes.test.ts
  • web/src/pages/TenantSwitcher.tsx

Comment thread src/web/routes/tenants.ts
Comment thread web/src/pages/TenantSwitcher.tsx
Comment thread web/src/pages/TenantSwitcher.tsx Outdated
Copy link
Copy Markdown
Owner Author

@blackms blackms left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed current head after addressing the CodeRabbit findings and merging main. Thread-aware review check shows 0 unresolved active threads; CI is green (build, lint, typecheck, tests) and CodeRabbit is green.

@blackms blackms merged commit 06a6346 into main May 29, 2026
6 checks passed
@blackms blackms deleted the pm-agent/aig-649-multi-tenancy branch May 29, 2026 12:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant