Skip to content

Akhil/add workspace - #230

Merged
itsmeakhil merged 80 commits into
mainfrom
akhil/add-workspace
Jun 29, 2026
Merged

Akhil/add workspace#230
itsmeakhil merged 80 commits into
mainfrom
akhil/add-workspace

Conversation

@itsmeakhil

Copy link
Copy Markdown
Collaborator

No description provided.

itsmeakhil and others added 30 commits June 26, 2026 13:22
Captures the data model, backend APIs, frontend surface, encryption
boundary, and migration strategy for the Organization → Workspace
hierarchy. Sub-project A only — collaboration / RBAC (B) and shared
workspace encryption (C) are explicitly deferred.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
26-task plan for the Organization → Workspace foundation. Phase 1
(backend foundation, T1-T4) is sequential. Phase 2 ships the workspace
API (T5). Phase 3 is the reference route refactor (T6 on passwords).
Phase 4 (T7-T20) refactors 14 scoped routes following the T6 pattern —
designed to dispatch in parallel via subagent-driven-development. Phase 5
(T21-T25) wires the frontend store, switcher pill, pinned-tools reshape,
sync, and migration banner. Phase 6 (T26) gates merge with manual
verification.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- seed.py: implement get_system_org_id() returning singleton org id or None
- repo.py: wrap insert_one in try/except DuplicateKeyError for upsert_org,
  upsert_org_membership, upsert_ws_membership; add ponytail comment on
  upsert_personal_workspace documenting tolerated race
- test_workspaces_seed.py: add 2 tests covering get_system_org_id
- pyproject.toml: set asyncio_default_fixture_loop_scope/test_loop_scope=session
  so Motor singleton shares one event loop across all async tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implement ensure_user_workspace_setup as an idempotent first-login hook that
provisions org membership, Personal workspace, and workspace membership for
new users. Add workspace_setup_at tracking to users collection and
mark_migration_pending flag for backfill tasks. Fix Python 3.9 union type
syntax throughout affected modules.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Revert the 5 files from commit 3d338a6 that were incorrectly modified
to support Python 3.9 compatibility. The project floor is Python 3.10+
per pyproject.toml, so the modern X | None union syntax is required.
Remove unnecessary from __future__ import annotations decorations from
auth/api.py and workspaces/services.py which legitimately added T3 logic.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds per-user backfill migration when a user first authenticates after deploy.
When triggered, background task stamps org_id + workspace_id on legacy docs in
14 workspace-scoped collections, and rewrites user_preferences.pinned_tools to
pinned_tools_by_workspace keyed by workspace_id. Idempotent via migrated_at.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implement 4 HTTP routes under /workspaces-api for orgs/workspaces:
- GET /orgs: list user's organizations
- GET /workspaces: list user's workspaces (opt. filtered by org_id)
- GET /workspaces/{workspace_id}: get single workspace details
- POST /workspaces/active: set active workspace cookie

Routes authenticate via get_current_uid, validate workspace membership,
and the POST route sets the active_workspace cookie (secure based on APP_ENV).

Wire new router into api_router with include_router pattern.

Add test fixtures authed_client and personal_ws_id for workspace route testing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace bare uid lookups with WorkspaceContext in passwords services and
route handlers. Reads use apply_legacy_or_filter for backfill tolerance;
writes stamp org_id/workspace_id/owner_uid; updates/deletes use
apply_workspace_filter. Removes uid-keyed @cached decorators. Adds
cross-user isolation invariant tests (2 new tests, 94 total passing).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace bare uid lookups with WorkspaceContext in bookmarks services and
route handlers. Reads use apply_legacy_or_filter for backfill tolerance;
writes stamp org_id/workspace_id/owner_uid; updates/deletes and point
lookups use apply_workspace_filter. Removes uid-keyed @cached decorators.
Adds cross-user isolation invariant tests (3 new tests, 97 total passing).
Updates test_audit_middleware.py to construct WorkspaceContext instead of
passing bare uid.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Detect pre-existing $or in base_filter and combine via $and to prevent
silent overwriting. Fixes bookmarks "uncategorized" query returning all
items instead of folderId=None items. This pattern protects every current
and future caller.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace bare uid lookups with WorkspaceContext in tasks/projects services
and route handlers. List/stat/export reads use apply_legacy_or_filter for
backfill tolerance; writes stamp org_id/workspace_id/owner_uid; point
lookups and mutations use apply_workspace_filter. Removes uid-keyed
@cached decorators and bump_version calls. Adds cross-user isolation
invariant tests for tasks and projects (3 new tests, 101 total passing).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace bare uid lookups with WorkspaceContext in notes services and
route handlers. Reads use apply_legacy_or_filter for backfill tolerance;
writes stamp org_id/workspace_id/owner_uid; updates/deletes use
apply_workspace_filter. Removes uid-keyed @cached decorators. Adds
cross-user isolation invariant tests (2 new tests, 103 total passing).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The original BACKFILL_COLLECTIONS list hardcoded user_field="uid" for 17
of 21 entries, but every workspace-scoped service in the codebase stamps
its docs with created_by, not uid. The backfill predicate matched zero
docs for those collections, so legacy rows never received org_id +
workspace_id stamps and apply_legacy_or_filter's transitional OR branch
would remain load-bearing indefinitely.

Audit performed against each route's services.py: every collection
uses created_by. Updated the spec accordingly. Also corrected the
seed_legacy_user_data fixture and test_backfill_stamps_legacy_rows
to use created_by for NOTES rows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace bare uid lookups with WorkspaceContext in environment_manager
services and route handlers. List reads use apply_legacy_or_filter for
backfill tolerance; writes stamp org_id/workspace_id/owner_uid; point
lookups and updates/deletes use apply_workspace_filter (strict). Adds
cross-user isolation invariant tests (2 new tests, 105 total passing).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace bare uid lookups with WorkspaceContext in api_key_vault services
and route handlers. List reads use apply_legacy_or_filter for backfill
tolerance; writes stamp org_id/workspace_id/owner_uid; point lookups and
updates/deletes use apply_workspace_filter (strict). Adds cross-user
isolation invariant tests (2 new tests, 106 total passing).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Migrate CODE_SNIPPETS route to WorkspaceContext per Task 12. Apply workspace
isolation filters, stamp 4 workspace fields, and remove cache decorators.
Mechanical refactor mirroring T6/T10/T11 pattern. Tests pass: 109 passed (107 + 2 new).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Migrate NOSQL_CONNECTIONS route to WorkspaceContext per Task 13. Apply workspace
isolation filters, stamp 4 workspace fields to inserts, and rename upsert_connection
to create_connection. Mechanical refactor mirroring T6/T10/T11/T12 pattern. Tests pass:
110 passed (107 + 2 new isolation tests = 111).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…mmary

T13 implementer renamed upsert_connection → create_connection (cosmetic;
function was always insert-only); the route summary still said
"(upsert)". Drop the parenthetical to match actual semantics.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Migrate API_CLIENT_COLLECTIONS, API_CLIENT_ENVIRONMENTS, API_CLIENT_HISTORY,
and API_CLIENT_WORKSPACES (the tool's internal workspace grouping) to use
WorkspaceContext per T14. Reads use apply_legacy_or_filter; point lookups and
mutations use apply_workspace_filter; inserts stamp 4 workspace fields.
API_CLIENT_PUBLIC_MOCKS is intentionally left unscoped — mocks are globally
shareable content readable anonymously by mock_id token. Drops uid-keyed
@cached decorators with ponytail comments. Updates collections_delta.py to
use get_workspace_ctx. Adds 3 cross-user isolation tests; updates existing
collections_delta tests to use get_workspace_ctx override.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Migrate sql_client route (SQL_CONNECTIONS) from uid-based filtering to
WorkspaceContext, mirroring T13 (nosql). Applies workspace isolation
rules: apply_legacy_or_filter for reads, apply_workspace_filter for
point lookups/mutations; stamps inserts with 4 fields (created_by, org_id,
workspace_id, owner_uid).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Migrate s3_drive route (S3_CONNECTIONS) from uid-based filtering to
WorkspaceContext, mirroring T15 (sql_client). Applies workspace isolation
rules: apply_legacy_or_filter for reads, apply_workspace_filter for
point lookups/mutations; stamps inserts with 4 fields (created_by, org_id,
workspace_id, owner_uid).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Migrate redis_commander route (REDIS_CONNECTIONS) from uid-based filtering to
WorkspaceContext, mirroring T15 (sql_client) and T16 (s3_drive). Applies workspace
isolation rules: apply_legacy_or_filter for reads, apply_workspace_filter for
point lookups/mutations; stamps inserts with 4 fields (created_by, org_id,
workspace_id, owner_uid). All endpoints now accept WorkspaceContext instead of uid.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Migrate URL_LINKS and URL_CLICK_EVENTS to use WorkspaceContext per T18.
Reads use apply_legacy_or_filter; point lookups and mutations use apply_workspace_filter;
inserts stamp 4 workspace fields. Public redirect paths stay anonymous.
Click events inherit the link owner's workspace stamps for analytics queries.
Drops uid-keyed @cached decorators with ponytail comments.
Adds 3 cross-user isolation tests and click event inheritance verification.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Apply workspace isolation pattern to JSON formatter documents: list/scan queries use apply_legacy_or_filter for backward compatibility, point lookups use apply_workspace_filter for strict scoping. Inserts stamp org_id, workspace_id, owner_uid alongside created_by. Add 2 isolation tests to verify cross-user and forged-workspace-id protection.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…cope

DNS lookup is a pure RPC with no per-user persistence — resolves DNS records
and returns results without database interaction or workspace context.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reshapes pinned-tools-store from a flat `pinnedTools: string[]` to a
workspace-keyed `pinnedByWorkspace: Record<string, string[]>` shape.
Bumps persist version to 2 with a migrate callback that drops legacy
local pins (server is source of truth via T24 preferences sync).
Adds `usePinnedToolsForActiveWorkspace` selector. Updates sidebar to
use the new selector and clears workspace store on sign-out. Adds
compile shims to nav-group, tool-header, dashboard, command-palette,
and preferences-sync (all marked TODO(T24) for the full rewrite).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
itsmeakhil and others added 27 commits June 27, 2026 16:46
- Add PendingInvitationsBadge component that polls listPending() every 30s,
  shows a red count chip, and provides per-row Accept buttons that call
  acceptInvitation → loadFromBackend → setActiveWorkspace with sonner toasts.
- Mount PendingInvitationsBadge in WorkspaceSwitcherDropdown alongside the
  workspace pill so the bell badge is visible in the navbar.
- Wire handleInviteToken into LoginForm: after OAuth/passkey auth succeeds,
  reads ?invite=<token> from the URL, calls acceptInvitation, re-hydrates the
  store, switches to the invited workspace, and toasts; falls back to
  /dashboard on error or when no token is present.
- Add 30 structural Jest tests covering null render, polling, accept flow,
  source structure, switcher integration, and login auto-accept logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… works

PendingInvitationsBadge was calling acceptInvitation(invitation.id), but the
backend route POST /invitations/{token}/accept expects the token field, not id.
Added token to InvitationOut schema and updated frontend Invitation type to
include it, so the component can pass the correct token to the accept API.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds EncryptedToolPlaceholder component and gates password-manager,
environment-manager, and api-keys pages behind it when the active
workspace is not personal. 32 structural tests added; one pre-existing
virtual-history-list failure is unrelated to this change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implement cross-tab workspace synchronization using BroadcastChannel API.
- Added workspace-broadcast.ts module with BroadcastChannel utilities
- Integrated broadcast messaging into workspace-store actions
- broadcastWorkspaceChanged() posts workspace changes to other tabs
- subscribeOnce() listens for cross-tab broadcasts and syncs state
- Comprehensive tests for both modules (18 tests passing)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add workspace-crypto.ts with ECDH keypair generation (X25519 preferred,
P-256 fallback), HKDF-derived KEK for DEK wrapping, AES-GCM-256 for entry
encryption, and SHA-256 DEK fingerprinting. Companion Jest round-trip tests
cover encrypt/decrypt, two-user wrap/unwrap, private-key seal/unseal,
fingerprint determinism, and wrong-key rejection (14 tests).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add crypto_repo.py with set/get_user_encryption helpers, EncryptionBlob/
KeypairOut/KeypairPostRequest schemas, GET+POST /users/me/keypair routes,
and round-trip integration tests. set_user_encryption uses upsert=True
so the write succeeds even when no prior USERS doc exists for the uid.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds workspace_memberships.wrappedDek field support and four crypto
routes: GET/POST dek-wrap, POST rotate-dek, GET pending-wraps.
Extends crypto_repo with set/get membership wrap, bulk update, workspace
encryption update, and pending-wrap lookup helpers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eserve createdAt

- Add admin gate to GET /workspaces/{ws_id}/pending-wraps matching siblings (post_dek_wrap_for_member, rotate_dek)
- Fix find_pending_wraps query to match both wrappedDek=None and missing field using $or + $exists
- Preserve createdAt on workspace encryption rotation, only setting it on first creation

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implements C-T4: adds useUserKeypairStore (in-memory public/private key + hydrated flag), user-keypair-api.ts (getKeypair/setKeypair), and wires hydration into MasterPasswordGate so the private key is AES-unwrapped from the server blob immediately after master-key unlock. Clears keypair state on signout alongside existing master-key clear.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s 404

Fixes two security/spec issues:
1. handleSignOut now clears the user keypair store alongside other sensitive stores
2. getKeypair returns null on HTTP 404 (no keypair yet) instead of throwing

Adds test: getKeypair returns null on HTTP 404

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implements C-T5: in-memory Map<workspaceId, {key, version}> with lazy
fetch+unwrap on first getDek() call; cleared on signout via both the
logout-user helper and the sidebar's handleSignOut.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…paces

Add getCipherKey() helper and useCipherKey() hook so the Password Manager
routes encryption through the workspace DEK (shared workspaces) or the
master key (personal workspaces). Wire in AddPasswordDialog,
EditPasswordDialog, and the page-level loadPasswords call as representative
call sites; add conditional placeholder logic gated on settings.encryption.
Add six unit tests covering all getCipherKey() branches.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…holder return

Move all hook calls (useAuth, useMasterKeyStore, useVaultGuard, usePasswordStore, useIsMobile, useRef, useEffect) above the early return for EncryptedToolPlaceholder to comply with Rules of Hooks. Ensures hook count remains consistent across renders regardless of workspace encryption settings.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…rkspaces

Extract useCipherKey to a shared lib (src/lib/use-cipher-key.ts) so
env-manager and future api-key-vault can consume it without duplicating
logic; password-manager/encryption-context.ts now re-exports from there.
Migrate AddEnvironmentSetDialog and EditEnvironmentSetDialog to useCipherKey.
Hoist all hooks above the EncryptedToolPlaceholder early-return in
environment-manager/page.tsx (Rules of Hooks fix, mirrors T25) and extend
the gate to the C-T9 forward-compat form (!is_personal && !settings?.encryption).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Migrate API Key Vault (page + dialogs) to use useCipherKey() instead of master-key store, following the T25 (password-manager) and T26 (environment-manager) pattern. Hoist all hooks above conditional placeholders to follow React Rules of Hooks. Update gate to forward-compat form that checks settings?.encryption for C-T9 toggle support. Update test to match environment-manager's forward-compat assertion.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implements Task 28 / C-T9 of the Workspaces E2EE plan:
- Backend: GET /workspaces/{ws_id}/member-publickeys (admin-only) returns
  all workspace members with their publicKey (or null if keypair not yet
  published), reusing PendingWrapOut schema from C-T3.
- Frontend: adds listMemberPublicKeys() and rotateDek() to workspace-dek-api.ts.
- Frontend: EnableEncryptedToolsCta component — generates DEK, wraps it for
  all members with published keypairs, submits rotate-dek, reloads store.
- Mounts CTA in workspace-section.tsx for shared, non-encrypted workspaces
  where the current user is an admin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace empty salt string with actual master vault salt from useMasterKeyStore
to satisfy backend KeypairPostRequest validation (Field min_length=1 on salt).
Add null guard for vault initialization and update test mocks.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implements C-T10: adds listPendingWraps/postDekWrap to workspace-dek-api,
creates PendingWrapsPrompt component (mounted for admins in WorkspaceSection
when encryption is initialized), and structural + logic tests for the prompt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds RotateKeyButton component (C-T11/Task-30): confirmation dialog +
client-side DEK rotation flow (generate new DEK, wrap for all members
with keypairs, atomic flip via rotate-dek API, evict cached DEK, reload
store). Mounts in WorkspaceSection for non-personal workspaces with
encryption enabled, admin role only. 32 new tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements C-T12: adds dek-rotation.ts with reencryptAllEntries() that
iterates all encrypted entries (passwords, env sets, api-key vault),
decrypts with the OLD DEK, re-encrypts with the NEW DEK, and PATCHes
each back. Updates rotate-key-button to capture the old DEK before
rotation, run the re-encryption loop after rotateDek() succeeds, and
show per-tool progress toasts. Adds 30 new tests for the rotation loop.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… gear

Mounts WorkspaceQuickActions adjacent to the workspace switcher pill in
the navbar. Adds two quick actions matching the Hoppscotch convention:
- "+" button → opens CreateOrgDialog directly (one-click create)
- Settings gear → links to /settings/workspaces

Hoppscotch docs state the switcher, "+", and settings all live in the
top-right corner. Our switcher was already top-right (sub-project A T22);
this completes the cluster.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…en DEK enabled

- TaskContainer global keydown handler: guard `event.key?.toLowerCase()`
  to avoid TypeError when event.key is undefined (composition events,
  some browser-extension synthetic events).

- WorkspaceContext gains `has_encryption: bool`, populated from
  workspaces.settings.encryption in get_workspace_ctx.

- has_permission (backend + frontend) flips encrypted-tool RBAC row
  from all-empty to plaintext-row semantics once the workspace has
  initialized encryption. Resolves "Role admin lacks write on
  api-key-vault" after a shared workspace runs Enable encrypted tools.

This is the deferred behavior the C-T1 ponytail comment named:
"in C this row flips per workspace once a DEK exists."

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…→ BufferSource

Vercel build failed under tighter TS DOM types. Five touchpoints fixed:

- workspace-crypto.ts: add bs() helper that slice-copies Uint8Array into
  an explicit ArrayBuffer, satisfying BufferSource at every subtle.encrypt
  / subtle.decrypt / subtle.importKey / HKDF salt+info / TextEncoder().encode
  call site.
- workspace-crypto.test.ts: replace unused @ts-expect-error with explicit
  cast on the Buffer polyfill.
- enable-encrypted-tools-cta / pending-wraps-prompt tests: type literal
  `null` via `as { salt: string } | null` so optional-chain narrowing
  doesn't collapse to `never`.
- migration-banner test: explicit `string` type on status literals so
  the comparison against "pending" overlaps.
- rotate-key-button test: typed `err: unknown` so `instanceof Error`
  is permitted.

TypeScript `tsc --noEmit` now clean. Pre-existing test-suite failures
(workspace-switcher-dropdown, workspace-store, encrypted-tool-placeholder,
pending-invitations-badge, virtual-history-list) are stale assertions
unrelated to the build error and tracked separately.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mydevtools-tech Ready Ready Preview, Comment Jun 29, 2026 9:32am

@itsmeakhil
itsmeakhil merged commit 49ba804 into main Jun 29, 2026
2 checks passed
@itsmeakhil
itsmeakhil deleted the akhil/add-workspace branch August 12, 2026 14:19
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