URL as the sole source of truth for active tenant
Replace JWT-driven, profile-backed "active tenant" state with a URL-first model. Per-tab and per-device independence by construction; switchTenant becomes pure navigation.
See the full design document in documents/work/2026-04-17-tenant-url-source-of-truth/spec.md and the accompanying ADR-0003.
Problem
A request's active tenant currently has four coupled sources: URL header, cookie, JWT claim, and profile column. The Node layer prefers URL; RLS prefers JWT. They can disagree, and when they do, data from one tenant can bleed into a page scoped to another. The 2026-04-16 prefetch incident was the headline failure; cross-device drift is a silent second failure mode caused by auth.users.raw_app_meta_data.active_tenant_id being a shared row.
Solution
URL is the sole authorization input for tenant. JWT claim, profile column, and "active" cookie are deleted or demoted. The database reads the URL-derived tenant via a PostgREST db-pre-request hook that pins a transaction-local GUC. get_active_tenant_id() returns the GUC, no fallback.
APIs pass the tenant explicitly via URL path (/api/v1/t/<slug>/...) or tenant-embedded API keys (amble_<slug>_<random>). MCP uses OAuth 2.1 with tenant pinned in the token's audience claim (per MCP spec 2025-06-18).
Acceptance highlights
- Two-tab test: user in tenants A and B opens
/t/aand/t/b— each tab stays in its own tenant across any sequence of actions. - Cross-device test: user signed into tenant A on laptop and tenant B on phone — switching on one device does not affect the other.
/api/tenants/switchroute is deleted. Tenant switcher becomes<Link>.profiles.active_tenant_idcolumn is dropped.auth.users.raw_app_meta_data.active_tenant_idis no longer written by any code path.- pgTAP test covers
private.set_tenant_context()happy path and deny path.
See the full spec for all acceptance criteria and the four-step rollout plan.
Supersedes
jwt-tenant-remove-profile-fallback.mdx — that spec removes only the profile fallback but keeps JWT as the runtime source. This one removes both.
Entity Sharing System
Per-entity visibility control, Google Drive-style sharing UI, share tokens for public access, and slug-based entity routing.
Sprinter Platform Package Migration
Compare Amble to the live Sprinter Platform package and registry surface, identify where Sprinter is ahead, and define a batch migration plan to consume @sprinterai packages and selective registry UI.