mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-21 05:02:17 +00:00
parent
400f9a214e
commit
99abb9d42e
171 changed files with 7260 additions and 7342 deletions
|
|
@ -28,7 +28,7 @@ The goal is not just to make individual endpoints faster. The goal is to move Fo
|
|||
|
||||
### Workbench
|
||||
|
||||
- `getWorkbench` still represents a monolithic workspace read that aggregates repo, project, and task state.
|
||||
- `getWorkbench` still represents a monolithic organization read that aggregates repo, repository, and task state.
|
||||
- The remote workbench store still responds to every event by pulling a full fresh snapshot.
|
||||
- Some task/workbench detail is still too expensive to compute inline and too broad to refresh after every mutation.
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ Requests should not block on provider calls, repo sync, sandbox provisioning, tr
|
|||
### View-model rule
|
||||
|
||||
- App shell view connects to app/session state and only the org actors visible on screen.
|
||||
- Workspace/task-list view connects to a workspace-owned summary projection.
|
||||
- Organization/task-list view connects to a organization-owned summary projection.
|
||||
- Task detail view connects directly to the selected task actor.
|
||||
- Sandbox/session detail connects only when the user opens that detail.
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ The app shell should stop using `/app/snapshot` as the steady-state read model.
|
|||
|
||||
#### Changes
|
||||
|
||||
1. Introduce a small app-shell projection owned by the app workspace actor:
|
||||
1. Introduce a small app-shell projection owned by the app organization actor:
|
||||
- auth status
|
||||
- current user summary
|
||||
- active org id
|
||||
|
|
@ -121,7 +121,7 @@ The app shell should stop using `/app/snapshot` as the steady-state read model.
|
|||
|
||||
#### Likely files
|
||||
|
||||
- `foundry/packages/backend/src/actors/workspace/app-shell.ts`
|
||||
- `foundry/packages/backend/src/actors/organization/app-shell.ts`
|
||||
- `foundry/packages/client/src/backend-client.ts`
|
||||
- `foundry/packages/client/src/remote/app-client.ts`
|
||||
- `foundry/packages/shared/src/app-shell.ts`
|
||||
|
|
@ -133,42 +133,42 @@ The app shell should stop using `/app/snapshot` as the steady-state read model.
|
|||
- Selecting an org returns quickly and the UI updates from actor events.
|
||||
- App shell refresh cost is bounded by visible state, not every eligible organization on every poll.
|
||||
|
||||
### 3. Workspace summary becomes a projection, not a full snapshot
|
||||
### 3. Organization summary becomes a projection, not a full snapshot
|
||||
|
||||
The task list should read a workspace-owned summary projection instead of calling into every task actor on each refresh.
|
||||
The task list should read a organization-owned summary projection instead of calling into every task actor on each refresh.
|
||||
|
||||
#### Changes
|
||||
|
||||
1. Define a durable workspace summary model with only list-screen fields:
|
||||
1. Define a durable organization summary model with only list-screen fields:
|
||||
- repo summary
|
||||
- project summary
|
||||
- repository summary
|
||||
- task summary
|
||||
- selected/open task ids
|
||||
- unread/session status summary
|
||||
- coarse git/PR state summary
|
||||
2. Update workspace actor workflows so task/project changes incrementally update this projection.
|
||||
2. Update organization actor workflows so task/repository changes incrementally update this projection.
|
||||
3. Change `getWorkbench` to return the projection only.
|
||||
4. Change `workbenchUpdated` from "invalidate and refetch everything" to "here is the updated projection version or changed entity ids".
|
||||
5. Remove task-actor fan-out from the default list read path.
|
||||
|
||||
#### Likely files
|
||||
|
||||
- `foundry/packages/backend/src/actors/workspace/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/project/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/organization/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/repository/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/task/index.ts`
|
||||
- `foundry/packages/backend/src/actors/task/workbench.ts`
|
||||
- task/workspace DB schema and migrations
|
||||
- task/organization DB schema and migrations
|
||||
- `foundry/packages/client/src/remote/workbench-client.ts`
|
||||
|
||||
#### Acceptance criteria
|
||||
|
||||
- Workbench list refresh does not call every task actor.
|
||||
- A websocket event does not force a full cross-actor rebuild.
|
||||
- Initial task-list load time scales roughly with workspace summary size, not repo count times task count times detail reads.
|
||||
- Initial task-list load time scales roughly with organization summary size, not repo count times task count times detail reads.
|
||||
|
||||
### 4. Task detail moves to direct actor reads and events
|
||||
|
||||
Heavy task detail should move out of the workspace summary and into the selected task actor.
|
||||
Heavy task detail should move out of the organization summary and into the selected task actor.
|
||||
|
||||
#### Changes
|
||||
|
||||
|
|
@ -258,7 +258,7 @@ Do not delete bootstrap endpoints first. Shrink them after the subscription mode
|
|||
4. `06-daytona-provisioning-staged-background-flow.md`
|
||||
5. App shell realtime subscription model
|
||||
6. `02-repo-overview-from-cached-projection.md`
|
||||
7. Workspace summary projection
|
||||
7. Organization summary projection
|
||||
8. `04-workbench-session-creation-without-inline-provisioning.md`
|
||||
9. `05-workbench-snapshot-from-derived-state.md`
|
||||
10. Task-detail direct actor reads/subscriptions
|
||||
|
|
@ -270,7 +270,7 @@ Do not delete bootstrap endpoints first. Shrink them after the subscription mode
|
|||
- Runtime hardening removes the most dangerous correctness bug before more UI load shifts onto actor connections.
|
||||
- The first async workflow items reduce the biggest user-visible stalls quickly.
|
||||
- App shell realtime is smaller and lower-risk than the workbench migration, and it removes the current polling loop.
|
||||
- Workspace summary and task-detail split should happen after the async workflow moves so the projection model does not encode old synchronous assumptions.
|
||||
- Organization summary and task-detail split should happen after the async workflow moves so the projection model does not encode old synchronous assumptions.
|
||||
- Auth simplification is valuable but not required to remove the current refresh/polling/runtime problems.
|
||||
|
||||
## Observability Requirements
|
||||
|
|
@ -291,7 +291,7 @@ Each log line should include a request id or actor/event correlation id where po
|
|||
|
||||
1. Ship runtime hardening and observability first.
|
||||
2. Ship app-shell realtime behind a client flag while keeping snapshot bootstrap.
|
||||
3. Ship workspace summary projection behind a separate flag.
|
||||
3. Ship organization summary projection behind a separate flag.
|
||||
4. Migrate one heavy detail pane at a time off the monolithic workbench payload.
|
||||
5. Remove polling once the matching event path is proven stable.
|
||||
6. Only then remove or demote the old snapshot-heavy steady-state flows.
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ That makes a user-facing action depend on queue-backed and provider-backed work
|
|||
|
||||
## Current Code Context
|
||||
|
||||
- Workspace entry point: `foundry/packages/backend/src/actors/workspace/actions.ts`
|
||||
- Project task creation path: `foundry/packages/backend/src/actors/project/actions.ts`
|
||||
- Organization entry point: `foundry/packages/backend/src/actors/organization/actions.ts`
|
||||
- Repository task creation path: `foundry/packages/backend/src/actors/repository/actions.ts`
|
||||
- Task action surface: `foundry/packages/backend/src/actors/task/index.ts`
|
||||
- Task workflow: `foundry/packages/backend/src/actors/task/workflow/index.ts`
|
||||
- Task init/provision steps: `foundry/packages/backend/src/actors/task/workflow/init.ts`
|
||||
|
|
@ -33,8 +33,8 @@ That makes a user-facing action depend on queue-backed and provider-backed work
|
|||
- persisting any immediately-known metadata
|
||||
- returning the current task record
|
||||
3. After initialize completes, enqueue `task.command.provision` with `wait: false`.
|
||||
4. Change `workspace.createTask` to:
|
||||
- create or resolve the project
|
||||
4. Change `organization.createTask` to:
|
||||
- create or resolve the repository
|
||||
- create the task actor
|
||||
- call `task.initialize(...)`
|
||||
- stop awaiting `task.provision(...)`
|
||||
|
|
@ -51,12 +51,12 @@ That makes a user-facing action depend on queue-backed and provider-backed work
|
|||
|
||||
## Files Likely To Change
|
||||
|
||||
- `foundry/packages/backend/src/actors/workspace/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/project/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/organization/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/repository/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/task/index.ts`
|
||||
- `foundry/packages/backend/src/actors/task/workflow/index.ts`
|
||||
- `foundry/packages/backend/src/actors/task/workflow/init.ts`
|
||||
- `foundry/packages/frontend/src/components/workspace-dashboard.tsx`
|
||||
- `foundry/packages/frontend/src/components/organization-dashboard.tsx`
|
||||
- `foundry/packages/client/src/remote/workbench-client.ts`
|
||||
|
||||
## Client Impact
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ The frontend polls repo overview repeatedly, so this design multiplies slow work
|
|||
|
||||
## Current Code Context
|
||||
|
||||
- Workspace overview entry point: `foundry/packages/backend/src/actors/workspace/actions.ts`
|
||||
- Project overview implementation: `foundry/packages/backend/src/actors/project/actions.ts`
|
||||
- Branch sync poller: `foundry/packages/backend/src/actors/project-branch-sync/index.ts`
|
||||
- PR sync poller: `foundry/packages/backend/src/actors/project-pr-sync/index.ts`
|
||||
- Repo overview client polling: `foundry/packages/frontend/src/components/workspace-dashboard.tsx`
|
||||
- Organization overview entry point: `foundry/packages/backend/src/actors/organization/actions.ts`
|
||||
- Repository overview implementation: `foundry/packages/backend/src/actors/repository/actions.ts`
|
||||
- Branch sync poller: `foundry/packages/backend/src/actors/repository-branch-sync/index.ts`
|
||||
- PR sync poller: `foundry/packages/backend/src/actors/repository-pr-sync/index.ts`
|
||||
- Repo overview client polling: `foundry/packages/frontend/src/components/organization-dashboard.tsx`
|
||||
|
||||
## Target Contract
|
||||
|
||||
|
|
@ -30,27 +30,27 @@ The frontend polls repo overview repeatedly, so this design multiplies slow work
|
|||
## Proposed Fix
|
||||
|
||||
1. Remove inline `forceProjectSync()` from `getRepoOverview`.
|
||||
2. Add freshness fields to the project projection, for example:
|
||||
2. Add freshness fields to the repository projection, for example:
|
||||
- `branchSyncAt`
|
||||
- `prSyncAt`
|
||||
- `branchSyncStatus`
|
||||
- `prSyncStatus`
|
||||
3. Let the existing polling actors own cache refresh.
|
||||
4. If the client needs a manual refresh, add a non-blocking command such as `project.requestOverviewRefresh` that:
|
||||
4. If the client needs a manual refresh, add a non-blocking command such as `repository.requestOverviewRefresh` that:
|
||||
- enqueues refresh work
|
||||
- updates sync status to `queued` or `running`
|
||||
- returns immediately
|
||||
5. Keep `getRepoOverview` as a pure read over project SQLite state.
|
||||
5. Keep `getRepoOverview` as a pure read over repository SQLite state.
|
||||
|
||||
## Files Likely To Change
|
||||
|
||||
- `foundry/packages/backend/src/actors/workspace/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/project/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/project/db/schema.ts`
|
||||
- `foundry/packages/backend/src/actors/project/db/migrations.ts`
|
||||
- `foundry/packages/backend/src/actors/project-branch-sync/index.ts`
|
||||
- `foundry/packages/backend/src/actors/project-pr-sync/index.ts`
|
||||
- `foundry/packages/frontend/src/components/workspace-dashboard.tsx`
|
||||
- `foundry/packages/backend/src/actors/organization/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/repository/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/repository/db/schema.ts`
|
||||
- `foundry/packages/backend/src/actors/repository/db/migrations.ts`
|
||||
- `foundry/packages/backend/src/actors/repository-branch-sync/index.ts`
|
||||
- `foundry/packages/backend/src/actors/repository-pr-sync/index.ts`
|
||||
- `foundry/packages/frontend/src/components/organization-dashboard.tsx`
|
||||
|
||||
## Client Impact
|
||||
|
||||
|
|
|
|||
|
|
@ -10,20 +10,20 @@ These flows depend on repo/network state and can take minutes. They should not h
|
|||
|
||||
## Current Code Context
|
||||
|
||||
- Workspace repo action entry point: `foundry/packages/backend/src/actors/workspace/actions.ts`
|
||||
- Project repo action implementation: `foundry/packages/backend/src/actors/project/actions.ts`
|
||||
- Branch/task index state lives in the project actor SQLite DB.
|
||||
- Organization repo action entry point: `foundry/packages/backend/src/actors/organization/actions.ts`
|
||||
- Repository repo action implementation: `foundry/packages/backend/src/actors/repository/actions.ts`
|
||||
- Branch/task index state lives in the repository actor SQLite DB.
|
||||
- Current forced sync uses the PR and branch polling actors before and after the action.
|
||||
|
||||
## Target Contract
|
||||
|
||||
- Repo-affecting actions are accepted quickly and run in the background.
|
||||
- The project actor owns a durable action record with progress and final result.
|
||||
- Clients observe status via project/task state instead of waiting for a single response.
|
||||
- The repository actor owns a durable action record with progress and final result.
|
||||
- Clients observe status via repository/task state instead of waiting for a single response.
|
||||
|
||||
## Proposed Fix
|
||||
|
||||
1. Introduce a project-level workflow/job model for repo actions, for example:
|
||||
1. Introduce a repository-level workflow/job model for repo actions, for example:
|
||||
- `sync_repo`
|
||||
- `restack_repo`
|
||||
- `restack_subtree`
|
||||
|
|
@ -49,11 +49,11 @@ These flows depend on repo/network state and can take minutes. They should not h
|
|||
|
||||
## Files Likely To Change
|
||||
|
||||
- `foundry/packages/backend/src/actors/workspace/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/project/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/project/db/schema.ts`
|
||||
- `foundry/packages/backend/src/actors/project/db/migrations.ts`
|
||||
- `foundry/packages/frontend/src/components/workspace-dashboard.tsx`
|
||||
- `foundry/packages/backend/src/actors/organization/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/repository/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/repository/db/schema.ts`
|
||||
- `foundry/packages/backend/src/actors/repository/db/migrations.ts`
|
||||
- `foundry/packages/frontend/src/components/organization-dashboard.tsx`
|
||||
- Any shared types in `foundry/packages/shared/src`
|
||||
|
||||
## Client Impact
|
||||
|
|
@ -70,5 +70,5 @@ These flows depend on repo/network state and can take minutes. They should not h
|
|||
## Implementation Notes
|
||||
|
||||
- Keep validation cheap in the request path; expensive repo inspection belongs in the workflow.
|
||||
- If job rows are added, decide whether they are project-owned only or also mirrored into history events for UI consumption.
|
||||
- If job rows are added, decide whether they are repository-owned only or also mirrored into history events for UI consumption.
|
||||
- Fresh-agent check: branch-backed task creation and explicit repo stack actions should use the same background job/status vocabulary where possible.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Creating a workbench tab currently provisions the whole task if no active sandbo
|
|||
|
||||
## Current Code Context
|
||||
|
||||
- Workspace workbench action entry point: `foundry/packages/backend/src/actors/workspace/actions.ts`
|
||||
- Organization workbench action entry point: `foundry/packages/backend/src/actors/organization/actions.ts`
|
||||
- Task workbench behavior: `foundry/packages/backend/src/actors/task/workbench.ts`
|
||||
- Task provision action: `foundry/packages/backend/src/actors/task/index.ts`
|
||||
- Sandbox session creation path: `foundry/packages/backend/src/actors/sandbox-instance/index.ts`
|
||||
|
|
@ -36,7 +36,7 @@ Creating a workbench tab currently provisions the whole task if no active sandbo
|
|||
|
||||
## Files Likely To Change
|
||||
|
||||
- `foundry/packages/backend/src/actors/workspace/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/organization/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/task/workbench.ts`
|
||||
- `foundry/packages/backend/src/actors/task/index.ts`
|
||||
- `foundry/packages/backend/src/actors/task/db/schema.ts`
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ The remote workbench client refreshes after each action and on update events, so
|
|||
|
||||
## Current Code Context
|
||||
|
||||
- Workspace workbench snapshot builder: `foundry/packages/backend/src/actors/workspace/actions.ts`
|
||||
- Organization workbench snapshot builder: `foundry/packages/backend/src/actors/organization/actions.ts`
|
||||
- Task workbench snapshot builder: `foundry/packages/backend/src/actors/task/workbench.ts`
|
||||
- Sandbox session event persistence: `foundry/packages/backend/src/actors/sandbox-instance/persist.ts`
|
||||
- Remote workbench client refresh loop: `foundry/packages/client/src/remote/workbench-client.ts`
|
||||
|
|
@ -43,7 +43,7 @@ The remote workbench client refreshes after each action and on update events, so
|
|||
|
||||
## Files Likely To Change
|
||||
|
||||
- `foundry/packages/backend/src/actors/workspace/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/organization/actions.ts`
|
||||
- `foundry/packages/backend/src/actors/task/workbench.ts`
|
||||
- `foundry/packages/backend/src/actors/task/db/schema.ts`
|
||||
- `foundry/packages/backend/src/actors/task/db/migrations.ts`
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ Authentication and user identity are conflated into a single `appSessions` table
|
|||
## Current Code Context
|
||||
|
||||
- Custom OAuth flow: `foundry/packages/backend/src/services/app-github.ts` (`buildAuthorizeUrl`, `exchangeCode`, `getViewer`)
|
||||
- Session + identity management: `foundry/packages/backend/src/actors/workspace/app-shell.ts` (`ensureAppSession`, `updateAppSession`, `initGithubSession`, `syncGithubOrganizations`)
|
||||
- Session schema: `foundry/packages/backend/src/actors/workspace/db/schema.ts` (`appSessions` table)
|
||||
- Session + identity management: `foundry/packages/backend/src/actors/organization/app-shell.ts` (`ensureAppSession`, `updateAppSession`, `initGithubSession`, `syncGithubOrganizations`)
|
||||
- Session schema: `foundry/packages/backend/src/actors/organization/db/schema.ts` (`appSessions` table)
|
||||
- Shared types: `foundry/packages/shared/src/app-shell.ts` (`FoundryUser`, `FoundryAppSnapshot`)
|
||||
- HTTP routes: `foundry/packages/backend/src/index.ts` (`resolveSessionId`, `/v1/auth/github/*`, all `/v1/app/*` routes)
|
||||
- Frontend session persistence: `foundry/packages/client/src/backend-client.ts` (`persistAppSessionId`, `x-foundry-session` header, `foundrySession` URL param extraction)
|
||||
|
|
@ -41,7 +41,7 @@ Authentication and user identity are conflated into a single `appSessions` table
|
|||
- BetterAuth uses a custom adapter that routes all DB operations through RivetKit actors.
|
||||
- Each user has their own actor. BetterAuth's `user`, `session`, and `account` tables live in the per-user actor's SQLite via `c.db`.
|
||||
- The adapter resolves which actor to target based on the primary key BetterAuth passes for each operation (user ID, session ID, account ID).
|
||||
- A lightweight **session index** on the app-shell workspace actor maps session tokens → user actor identity, so inbound requests can be routed to the correct user actor without knowing the user ID upfront.
|
||||
- A lightweight **session index** on the app-shell organization actor maps session tokens → user actor identity, so inbound requests can be routed to the correct user actor without knowing the user ID upfront.
|
||||
|
||||
### Canonical user record
|
||||
|
||||
|
|
@ -70,9 +70,9 @@ BetterAuth expects a single database. Foundry uses per-actor SQLite — each act
|
|||
|
||||
When an HTTP request arrives, the backend has a session token but doesn't know the user ID yet. BetterAuth calls adapter methods like `findSession(sessionId)` to resolve this. But which actor holds that session row?
|
||||
|
||||
**Solution: session index on the app-shell workspace actor.**
|
||||
**Solution: session index on the app-shell organization actor.**
|
||||
|
||||
The app-shell workspace actor (which already handles auth routing) maintains a lightweight index table:
|
||||
The app-shell organization actor (which already handles auth routing) maintains a lightweight index table:
|
||||
|
||||
```
|
||||
sessionIndex
|
||||
|
|
@ -83,7 +83,7 @@ sessionIndex
|
|||
|
||||
The adapter flow for session lookup:
|
||||
1. BetterAuth calls `findSession(sessionId)`.
|
||||
2. Adapter queries `sessionIndex` on the workspace actor to resolve `userActorKey`.
|
||||
2. Adapter queries `sessionIndex` on the organization actor to resolve `userActorKey`.
|
||||
3. Adapter gets the user actor handle and queries BetterAuth's `session` table in that actor's `c.db`.
|
||||
|
||||
The adapter flow for user creation (OAuth callback):
|
||||
|
|
@ -91,12 +91,12 @@ The adapter flow for user creation (OAuth callback):
|
|||
2. Adapter resolves the GitHub numeric ID from the user data.
|
||||
3. Adapter creates/gets the user actor keyed by GitHub ID.
|
||||
4. Adapter inserts into BetterAuth's `user` table in that actor's `c.db`.
|
||||
5. When `createSession` follows, adapter writes to the user actor's `session` table AND inserts into the workspace actor's `sessionIndex`.
|
||||
5. When `createSession` follows, adapter writes to the user actor's `session` table AND inserts into the organization actor's `sessionIndex`.
|
||||
|
||||
### User actor shape
|
||||
|
||||
```text
|
||||
UserActor (key: ["ws", workspaceId, "user", githubNumericId])
|
||||
UserActor (key: ["ws", organizationId, "user", githubNumericId])
|
||||
├── BetterAuth tables: user, session, account (managed by BetterAuth schema)
|
||||
├── userProfiles (app-specific: eligibleOrganizationIds, starterRepoStatus, roleLabel)
|
||||
└── sessionState (app-specific: activeOrganizationId per session)
|
||||
|
|
@ -127,15 +127,15 @@ The adapter must inspect `model` and `where` to determine the target actor:
|
|||
| Model | Routing strategy |
|
||||
|-------|-----------------|
|
||||
| `user` (by id) | User actor key derived directly from user ID |
|
||||
| `user` (by email) | `emailIndex` on workspace actor → user actor key |
|
||||
| `session` (by token) | `sessionIndex` on workspace actor → user actor key |
|
||||
| `session` (by id) | `sessionIndex` on workspace actor → user actor key |
|
||||
| `user` (by email) | `emailIndex` on organization actor → user actor key |
|
||||
| `session` (by token) | `sessionIndex` on organization actor → user actor key |
|
||||
| `session` (by id) | `sessionIndex` on organization actor → user actor key |
|
||||
| `session` (by userId) | User actor key derived directly from userId |
|
||||
| `account` | Always has `userId` in where or data → user actor key |
|
||||
| `verification` | Workspace actor (not user-scoped — used for email verification, password reset) |
|
||||
| `verification` | Organization actor (not user-scoped — used for email verification, password reset) |
|
||||
|
||||
On `create` for `session` model: write to user actor's `session` table AND insert into workspace actor's `sessionIndex`.
|
||||
On `delete` for `session` model: delete from user actor's `session` table AND remove from workspace actor's `sessionIndex`.
|
||||
On `create` for `session` model: write to user actor's `session` table AND insert into organization actor's `sessionIndex`.
|
||||
On `delete` for `session` model: delete from user actor's `session` table AND remove from organization actor's `sessionIndex`.
|
||||
|
||||
#### Adapter construction
|
||||
|
||||
|
|
@ -188,14 +188,14 @@ session: {
|
|||
|
||||
#### BetterAuth core tables
|
||||
|
||||
Four tables, all in the per-user actor's SQLite (except `verification` which goes on workspace actor):
|
||||
Four tables, all in the per-user actor's SQLite (except `verification` which goes on organization actor):
|
||||
|
||||
**`user`**: `id`, `name`, `email`, `emailVerified`, `image`, `createdAt`, `updatedAt`
|
||||
**`session`**: `id`, `token`, `userId`, `expiresAt`, `ipAddress?`, `userAgent?`, `createdAt`, `updatedAt`
|
||||
**`account`**: `id`, `userId`, `accountId` (GitHub numeric ID), `providerId` ("github"), `accessToken?`, `refreshToken?`, `scope?`, `createdAt`, `updatedAt`
|
||||
**`verification`**: `id`, `identifier`, `value`, `expiresAt`, `createdAt`, `updatedAt`
|
||||
|
||||
For `findUserByEmail`, a secondary index (email → user actor key) is needed on the workspace actor alongside `sessionIndex`.
|
||||
For `findUserByEmail`, a secondary index (email → user actor key) is needed on the organization actor alongside `sessionIndex`.
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
|
|
@ -210,12 +210,12 @@ Research confirms:
|
|||
|
||||
1. **Prototype the adapter + user actor end-to-end** — wire up `createAdapterFactory` with a minimal actor-routed implementation. Confirm that BetterAuth's GitHub OAuth flow completes successfully with user/session/account records landing in the correct per-user actor's SQLite.
|
||||
2. **Verify `findOne` for session model** — confirm the `where` clause BetterAuth passes for session lookup includes the `token` field (not just `id`), so the adapter can route via `sessionIndex` keyed by token.
|
||||
3. **Measure cookie-cached vs uncached request latency** — confirm that with cookie caching enabled, the adapter is not called on every request, and that the uncached fallback (workspace actor index → user actor → session table) is acceptable.
|
||||
3. **Measure cookie-cached vs uncached request latency** — confirm that with cookie caching enabled, the adapter is not called on every request, and that the uncached fallback (organization actor index → user actor → session table) is acceptable.
|
||||
|
||||
### Phase 1: User actor + adapter infrastructure (no behavior change)
|
||||
|
||||
1. **Install `better-auth` package** in `packages/backend`.
|
||||
2. **Define `UserActor`** with actor key `["ws", workspaceId, "user", githubNumericId]`. Include BetterAuth's required tables (`user`, `session`, `account`) plus app-specific tables in its schema.
|
||||
2. **Define `UserActor`** with actor key `["ws", organizationId, "user", githubNumericId]`. Include BetterAuth's required tables (`user`, `session`, `account`) plus app-specific tables in its schema.
|
||||
3. **Create `userProfiles` table** in user actor schema:
|
||||
```
|
||||
userProfiles
|
||||
|
|
@ -237,7 +237,7 @@ Research confirms:
|
|||
├── createdAt (integer)
|
||||
├── updatedAt (integer)
|
||||
```
|
||||
5. **Create `sessionIndex` and `emailIndex` tables** on the app-shell workspace actor:
|
||||
5. **Create `sessionIndex` and `emailIndex` tables** on the app-shell organization actor:
|
||||
```
|
||||
sessionIndex
|
||||
├── sessionId (text, PK)
|
||||
|
|
@ -256,7 +256,7 @@ Research confirms:
|
|||
### Phase 2: Migrate OAuth flow to BetterAuth
|
||||
|
||||
1. **Replace `startAppGithubAuth`** — delegate to BetterAuth's GitHub OAuth initiation instead of hand-rolling `buildAuthorizeUrl` + `oauthState` + `oauthStateExpiresAt`.
|
||||
2. **Replace `completeAppGithubAuth`** — delegate to BetterAuth's callback handler. BetterAuth creates/updates the user record in the user actor and creates a signed session. The adapter writes to `sessionIndex` on the workspace actor.
|
||||
2. **Replace `completeAppGithubAuth`** — delegate to BetterAuth's callback handler. BetterAuth creates/updates the user record in the user actor and creates a signed session. The adapter writes to `sessionIndex` on the organization actor.
|
||||
3. **After BetterAuth callback completes**, populate `userProfiles` in the user actor with app-specific fields and enqueue the slow org sync (same background workflow pattern as today).
|
||||
4. **Replace `signOutApp`** — delegate to BetterAuth session invalidation. Adapter removes entry from `sessionIndex`.
|
||||
5. **Update `resolveSessionId`** in `index.ts` — validate the session via BetterAuth (which routes through the adapter → `sessionIndex` → user actor). BetterAuth verifies the signature and checks expiration.
|
||||
|
|
@ -288,18 +288,18 @@ Research confirms:
|
|||
## Constraints
|
||||
|
||||
- **Actor-routed adapter.** BetterAuth does not natively support per-user actor databases. The custom adapter must route every DB operation to the correct actor. This adds a layer of indirection and latency (actor handle resolution + message) on adapter calls.
|
||||
- **Session index cost is mitigated by cookie caching.** With `cookieCache` enabled, BetterAuth validates sessions from a signed cookie on most requests — the adapter (and thus the `sessionIndex` lookup + user actor round-trip) is only called when the cache expires or on writes. Without caching, every authenticated request would hit the workspace actor's `sessionIndex` table then the user actor.
|
||||
- **Two-actor write on session create/destroy.** Creating or destroying a session requires writing to both the user actor (BetterAuth's `session` table) and the workspace actor (`sessionIndex`). These must be consistent — if the user actor write succeeds but the index write fails, the session exists but is unreachable.
|
||||
- **Session index cost is mitigated by cookie caching.** With `cookieCache` enabled, BetterAuth validates sessions from a signed cookie on most requests — the adapter (and thus the `sessionIndex` lookup + user actor round-trip) is only called when the cache expires or on writes. Without caching, every authenticated request would hit the organization actor's `sessionIndex` table then the user actor.
|
||||
- **Two-actor write on session create/destroy.** Creating or destroying a session requires writing to both the user actor (BetterAuth's `session` table) and the organization actor (`sessionIndex`). These must be consistent — if the user actor write succeeds but the index write fails, the session exists but is unreachable.
|
||||
- **Background org sync pattern must be preserved.** The fast-path/slow-path split (`initGithubSession` returns immediately, `syncGithubOrganizations` runs in workflow queue) is critical for avoiding proxy timeout retries. BetterAuth handles the OAuth exchange, but the org sync stays as a background workflow.
|
||||
- **`GitHubAppClient` is still needed.** BetterAuth replaces the OAuth user-auth flow, but installation tokens, webhook verification, repo listing, and org listing are GitHub App operations that BetterAuth does not cover.
|
||||
- **User ID migration.** Changing user IDs from `user-${slugify(login)}` to GitHub numeric IDs affects `organizationMembers`, `seatAssignments`, and any cross-actor references to user IDs. Existing data needs a migration path.
|
||||
- **`findUserByEmail` requires a secondary index.** BetterAuth sometimes looks up users by email (e.g., account linking). An `emailIndex` table on the workspace actor is needed. This must be kept in sync with the user actor's email field.
|
||||
- **`findUserByEmail` requires a secondary index.** BetterAuth sometimes looks up users by email (e.g., account linking). An `emailIndex` table on the organization actor is needed. This must be kept in sync with the user actor's email field.
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
- **Adapter call context — RESOLVED.** Research confirms BetterAuth adapter methods are plain async functions with no request context dependency. The adapter closes over the RivetKit registry at init time and resolves actor handles on demand. No ambient `c` context needed.
|
||||
- **Hot-path latency — MITIGATED.** Cookie caching (`cookieCache` with `strategy: "compact"`) means most authenticated requests validate the session from a signed cookie without calling the adapter at all. The adapter (and thus the actor round-trip) is only hit when the cache expires (configurable, e.g., every 5 minutes) or on writes. This makes the session index + user actor lookup acceptable.
|
||||
- **Two-actor consistency.** Session create/destroy touches two actors (user actor + workspace index). If either write fails, the system is in an inconsistent state. Recommended: write index first, then user actor. A dangling index entry pointing to a nonexistent session is benign — BetterAuth treats it as "session not found" and the user just re-authenticates.
|
||||
- **Two-actor consistency.** Session create/destroy touches two actors (user actor + organization index). If either write fails, the system is in an inconsistent state. Recommended: write index first, then user actor. A dangling index entry pointing to a nonexistent session is benign — BetterAuth treats it as "session not found" and the user just re-authenticates.
|
||||
- **Cookie vs header auth.** BetterAuth defaults to HTTP-only cookies (`better-auth.session_token`). The current system uses a custom `x-foundry-session` header with `localStorage`. BetterAuth supports `bearer` token mode for programmatic clients via its `bearer` plugin. Enable both for browser + API access.
|
||||
- **Dev bootstrap flow.** `bootstrapAppGithubSession` bypasses the normal OAuth flow for local development. BetterAuth supports programmatic session creation via its internal adapter — the dev path can call the adapter's `create` method directly for the `session` and `account` models.
|
||||
- **Actor lifecycle for users.** User actors are long-lived but low-traffic. RivetKit will idle/unload them. With cookie caching, cold-start only happens when the cache expires — not on every request. Acceptable.
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ The governing policy now lives in `foundry/CLAUDE.md`:
|
|||
- Backend actor entry points live under `foundry/packages/backend/src/actors`.
|
||||
- Provider-backed long-running work lives under `foundry/packages/backend/src/providers`.
|
||||
- The main UI consumers are:
|
||||
- `foundry/packages/frontend/src/components/workspace-dashboard.tsx`
|
||||
- `foundry/packages/frontend/src/components/organization-dashboard.tsx`
|
||||
- `foundry/packages/frontend/src/components/mock-layout.tsx`
|
||||
- `foundry/packages/client/src/remote/workbench-client.ts`
|
||||
- Existing non-blocking examples already exist in app-shell GitHub auth/import flows. Use those as the reference pattern for request returns plus background completion.
|
||||
|
|
@ -32,7 +32,7 @@ The governing policy now lives in `foundry/CLAUDE.md`:
|
|||
4. `06-daytona-provisioning-staged-background-flow.md`
|
||||
5. App shell realtime subscription work from `00-end-to-end-async-realtime-plan.md`
|
||||
6. `02-repo-overview-from-cached-projection.md`
|
||||
7. Workspace summary projection work from `00-end-to-end-async-realtime-plan.md`
|
||||
7. Organization summary projection work from `00-end-to-end-async-realtime-plan.md`
|
||||
8. `04-workbench-session-creation-without-inline-provisioning.md`
|
||||
9. `05-workbench-snapshot-from-derived-state.md`
|
||||
10. Task-detail direct subscription work from `00-end-to-end-async-realtime-plan.md`
|
||||
|
|
@ -42,7 +42,7 @@ The governing policy now lives in `foundry/CLAUDE.md`:
|
|||
|
||||
- Runtime hardening and the first async workflow items remove the highest-risk correctness and timeout issues first.
|
||||
- App shell realtime is a smaller migration than the workbench and removes the current polling loop early.
|
||||
- Workspace summary and task-detail subscription work are easier once long-running mutations already report durable background state.
|
||||
- Organization summary and task-detail subscription work are easier once long-running mutations already report durable background state.
|
||||
- Auth simplification is important, but it should not block the snapshot/polling/runtime fixes.
|
||||
|
||||
## Fresh Agent Checklist
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue