docs: add deployment modes documentation and update plans
Add DEPLOYMENT-MODES.md with canonical mode taxonomy. Update CLI.md, DEVELOPING.md, PRODUCT.md, and SPEC-implementation.md with local_trusted/ authenticated nomenclature. Revise humans-and-permissions plan with Better Auth choice, bootstrap flow, unified invite semantics, and expanded criteria. Add implementation guide and additional plan documents for cursor cloud adapter and deployment auth mode consolidation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
477
doc/plans/cursor-cloud-adapter.md
Normal file
477
doc/plans/cursor-cloud-adapter.md
Normal file
@@ -0,0 +1,477 @@
|
||||
# Cursor Cloud Agent Adapter — Technical Plan
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the V1 design for a Paperclip adapter that integrates with
|
||||
Cursor Background Agents via the Cursor REST API.
|
||||
|
||||
Primary references:
|
||||
|
||||
- https://docs.cursor.com/background-agent/api/overview
|
||||
- https://docs.cursor.com/background-agent/api
|
||||
- https://docs.cursor.com/background-agent/api/webhooks
|
||||
|
||||
Unlike `claude_local` and `codex_local`, this adapter is not a local subprocess.
|
||||
It is a remote orchestration adapter with:
|
||||
|
||||
1. launch/follow-up over HTTP
|
||||
2. webhook-driven status updates when possible
|
||||
3. polling fallback for reliability
|
||||
4. synthesized stdout events for Paperclip UI/CLI
|
||||
|
||||
## Key V1 Decisions
|
||||
|
||||
1. **Auth to Cursor API** uses `Authorization: Bearer <CURSOR_API_KEY>`.
|
||||
2. **Callback URL** must be publicly reachable by Cursor VMs:
|
||||
- local: Tailscale URL
|
||||
- prod: public server URL
|
||||
3. **Agent callback auth to Paperclip** uses a bootstrap exchange flow (no long-lived Paperclip key in prompt).
|
||||
4. **Webhooks are V1**, polling remains fallback.
|
||||
5. **Skill delivery** is fetch-on-demand from Paperclip endpoints, not full SKILL.md prompt injection.
|
||||
|
||||
---
|
||||
|
||||
## Cursor API Reference (Current)
|
||||
|
||||
Base URL: `https://api.cursor.com`
|
||||
|
||||
Authentication header:
|
||||
|
||||
- `Authorization: Bearer <CURSOR_API_KEY>`
|
||||
|
||||
Core endpoints:
|
||||
|
||||
| Endpoint | Method | Purpose |
|
||||
|---|---|---|
|
||||
| `/v0/agents` | POST | Launch agent |
|
||||
| `/v0/agents/{id}` | GET | Agent status |
|
||||
| `/v0/agents/{id}/conversation` | GET | Conversation history |
|
||||
| `/v0/agents/{id}/followup` | POST | Follow-up prompt |
|
||||
| `/v0/agents/{id}/stop` | POST | Stop/pause running agent |
|
||||
| `/v0/models` | GET | Recommended model list |
|
||||
| `/v0/me` | GET | API key metadata |
|
||||
| `/v0/repositories` | GET | Accessible repos (strictly rate-limited) |
|
||||
|
||||
Status handling policy for adapter:
|
||||
|
||||
- Treat `CREATING` and `RUNNING` as non-terminal.
|
||||
- Treat `FINISHED` as success terminal.
|
||||
- Treat `ERROR` as failure terminal.
|
||||
- Treat unknown non-active statuses as terminal failure and preserve raw status in `resultJson`.
|
||||
|
||||
Webhook facts relevant to V1:
|
||||
|
||||
- Cursor emits `statusChange` webhooks.
|
||||
- Terminal webhook statuses include `ERROR` and `FINISHED`.
|
||||
- Webhook signatures use HMAC SHA256 (`X-Webhook-Signature: sha256=...`).
|
||||
|
||||
Operational limits:
|
||||
|
||||
- `/v0/repositories`: 1 req/user/min, 30 req/user/hour.
|
||||
- MCP not supported in Cursor background agents.
|
||||
|
||||
---
|
||||
|
||||
## Package Structure
|
||||
|
||||
```
|
||||
packages/adapters/cursor-cloud/
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
└── src/
|
||||
├── index.ts
|
||||
├── api.ts
|
||||
├── server/
|
||||
│ ├── index.ts
|
||||
│ ├── execute.ts
|
||||
│ ├── parse.ts
|
||||
│ ├── test.ts
|
||||
│ └── webhook.ts
|
||||
├── ui/
|
||||
│ ├── index.ts
|
||||
│ ├── parse-stdout.ts
|
||||
│ └── build-config.ts
|
||||
└── cli/
|
||||
├── index.ts
|
||||
└── format-event.ts
|
||||
```
|
||||
|
||||
`package.json` uses standard four exports (`.`, `./server`, `./ui`, `./cli`).
|
||||
|
||||
---
|
||||
|
||||
## API Client (`src/api.ts`)
|
||||
|
||||
`src/api.ts` is a typed wrapper over Cursor endpoints.
|
||||
|
||||
```ts
|
||||
interface CursorClientConfig {
|
||||
apiKey: string;
|
||||
baseUrl?: string; // default https://api.cursor.com
|
||||
}
|
||||
|
||||
interface CursorAgent {
|
||||
id: string;
|
||||
name: string;
|
||||
status: "CREATING" | "RUNNING" | "FINISHED" | "ERROR" | string;
|
||||
source: { repository: string; ref: string };
|
||||
target: {
|
||||
branchName?: string;
|
||||
prUrl?: string;
|
||||
url?: string;
|
||||
autoCreatePr?: boolean;
|
||||
openAsCursorGithubApp?: boolean;
|
||||
skipReviewerRequest?: boolean;
|
||||
};
|
||||
summary?: string;
|
||||
createdAt: string;
|
||||
}
|
||||
```
|
||||
|
||||
Client requirements:
|
||||
|
||||
- send `Authorization: Bearer ...` on all requests
|
||||
- throw typed `CursorApiError` with `status`, parsed body, and request context
|
||||
- preserve raw unknown fields for debugging in error metadata
|
||||
|
||||
---
|
||||
|
||||
## Adapter Config Contract (`src/index.ts`)
|
||||
|
||||
```ts
|
||||
export const type = "cursor_cloud";
|
||||
export const label = "Cursor Cloud Agent";
|
||||
```
|
||||
|
||||
V1 config fields:
|
||||
|
||||
- `repository` (required): GitHub repo URL
|
||||
- `ref` (optional, default `main`)
|
||||
- `model` (optional, allow empty = auto)
|
||||
- `autoCreatePr` (optional, default `false`)
|
||||
- `branchName` (optional)
|
||||
- `promptTemplate` / `bootstrapPromptTemplate`
|
||||
- `pollIntervalSec` (optional, default `10`)
|
||||
- `timeoutSec` (optional, default `0`)
|
||||
- `graceSec` (optional, default `20`)
|
||||
- `paperclipPublicUrl` (optional override; else `PAPERCLIP_PUBLIC_URL` env)
|
||||
- `enableWebhooks` (optional, default `true`)
|
||||
- `env.CURSOR_API_KEY` (required, secret_ref preferred)
|
||||
- `env.CURSOR_WEBHOOK_SECRET` (required if `enableWebhooks=true`, min 32)
|
||||
|
||||
Important: do not store Cursor key in plain `apiKey` top-level field.
|
||||
Use `adapterConfig.env` so secret references are supported by existing secret-resolution flow.
|
||||
|
||||
---
|
||||
|
||||
## Paperclip Callback + Auth Flow (V1)
|
||||
|
||||
Cursor agents run remotely, so we cannot inject local env like `PAPERCLIP_API_KEY`.
|
||||
|
||||
### Public URL
|
||||
|
||||
The adapter must resolve a callback base URL in this order:
|
||||
|
||||
1. `adapterConfig.paperclipPublicUrl`
|
||||
2. `process.env.PAPERCLIP_PUBLIC_URL`
|
||||
|
||||
If empty, fail `testEnvironment` and runtime execution with a clear error.
|
||||
|
||||
### Bootstrap Exchange
|
||||
|
||||
Goal: avoid putting long-lived Paperclip credentials in prompt text.
|
||||
|
||||
Flow:
|
||||
|
||||
1. Before launch/follow-up, Paperclip mints a one-time bootstrap token bound to:
|
||||
- `agentId`
|
||||
- `companyId`
|
||||
- `runId`
|
||||
- short TTL (for example 10 minutes)
|
||||
2. Adapter includes only:
|
||||
- `paperclipPublicUrl`
|
||||
- exchange endpoint path
|
||||
- bootstrap token
|
||||
3. Cursor agent calls:
|
||||
- `POST /api/agent-auth/exchange`
|
||||
4. Paperclip validates bootstrap token and returns a run-scoped bearer JWT.
|
||||
5. Cursor agent uses returned bearer token for all Paperclip API calls.
|
||||
|
||||
This keeps long-lived keys out of prompt and supports clean revocation by TTL.
|
||||
|
||||
---
|
||||
|
||||
## Skills Delivery Strategy (V1)
|
||||
|
||||
Do not inline full SKILL.md content into the prompt.
|
||||
|
||||
Instead:
|
||||
|
||||
1. Prompt includes a compact instruction to fetch skills from Paperclip.
|
||||
2. After auth exchange, agent fetches:
|
||||
- `GET /api/skills/index`
|
||||
- `GET /api/skills/paperclip`
|
||||
- `GET /api/skills/paperclip-create-agent` when needed
|
||||
3. Agent loads full skill content on demand.
|
||||
|
||||
Benefits:
|
||||
|
||||
- avoids prompt bloat
|
||||
- keeps skill docs centrally updatable
|
||||
- aligns with how local adapters expose skills as discoverable procedures
|
||||
|
||||
---
|
||||
|
||||
## Execution Flow (`src/server/execute.ts`)
|
||||
|
||||
### Step 1: Resolve Config and Secrets
|
||||
|
||||
- parse adapter config via `asString/asBoolean/asNumber/parseObject`
|
||||
- resolve `env.CURSOR_API_KEY`
|
||||
- resolve `paperclipPublicUrl`
|
||||
- validate webhook secret when webhooks enabled
|
||||
|
||||
### Step 2: Session Resolution
|
||||
|
||||
Session identity is Cursor `agentId` (stored in `sessionParams`).
|
||||
Reuse only when repository matches.
|
||||
|
||||
### Step 3: Render Prompt
|
||||
|
||||
Render template as usual, then append a compact callback block:
|
||||
|
||||
- public Paperclip URL
|
||||
- bootstrap exchange endpoint
|
||||
- bootstrap token
|
||||
- skill index endpoint
|
||||
- required run header behavior
|
||||
|
||||
### Step 4: Launch/Follow-up
|
||||
|
||||
- on resume: `POST /followup`
|
||||
- else: `POST /agents`
|
||||
- include webhook object when enabled:
|
||||
- `url: <paperclipPublicUrl>/api/adapters/cursor-cloud/webhooks`
|
||||
- `secret: CURSOR_WEBHOOK_SECRET`
|
||||
|
||||
### Step 5: Progress + Completion
|
||||
|
||||
Use hybrid strategy:
|
||||
|
||||
- webhook events are primary status signal
|
||||
- polling is fallback and transcript source (`/conversation`)
|
||||
|
||||
Emit synthetic events to stdout (`init`, `status`, `assistant`, `user`, `result`).
|
||||
|
||||
Completion logic:
|
||||
|
||||
- success: `status === FINISHED`
|
||||
- failure: `status === ERROR` or unknown terminal
|
||||
- timeout: stop agent, mark timedOut
|
||||
|
||||
### Step 6: Result Mapping
|
||||
|
||||
`AdapterExecutionResult`:
|
||||
|
||||
- `exitCode: 0` on success, `1` on terminal failure
|
||||
- `errorMessage` populated on failure/timeout
|
||||
- `sessionParams: { agentId, repository }`
|
||||
- `provider: "cursor"`
|
||||
- `usage` and `costUsd`: unavailable/null
|
||||
- `resultJson`: include raw status/target/conversation snapshot
|
||||
|
||||
Also ensure `result` event is emitted to stdout before return.
|
||||
|
||||
---
|
||||
|
||||
## Webhook Handling (`src/server/webhook.ts` + server route)
|
||||
|
||||
Add a server endpoint to receive Cursor webhook deliveries.
|
||||
|
||||
Responsibilities:
|
||||
|
||||
1. Verify HMAC signature from `X-Webhook-Signature`.
|
||||
2. Deduplicate by `X-Webhook-ID`.
|
||||
3. Validate event type (`statusChange`).
|
||||
4. Route by Cursor `agentId` to active Paperclip run context.
|
||||
5. Append `heartbeat_run_events` entries for audit/debug.
|
||||
6. Update in-memory run signal so execute loop can short-circuit quickly.
|
||||
|
||||
Security:
|
||||
|
||||
- reject invalid signature (`401`)
|
||||
- reject malformed payload (`400`)
|
||||
- always return quickly after persistence (`2xx`)
|
||||
|
||||
---
|
||||
|
||||
## Environment Test (`src/server/test.ts`)
|
||||
|
||||
Checks:
|
||||
|
||||
1. `CURSOR_API_KEY` present
|
||||
2. key validity via `GET /v0/me`
|
||||
3. repository configured and URL shape valid
|
||||
4. model exists (if set) via `/v0/models`
|
||||
5. `paperclipPublicUrl` present and reachable shape-valid
|
||||
6. webhook secret present/length-valid when webhooks enabled
|
||||
|
||||
Repository-access verification via `/v0/repositories` should be optional due strict rate limits.
|
||||
Use a warning-level check only when an explicit `verifyRepositoryAccess` option is set.
|
||||
|
||||
---
|
||||
|
||||
## UI + CLI
|
||||
|
||||
### UI parser (`src/ui/parse-stdout.ts`)
|
||||
|
||||
Handle event types:
|
||||
|
||||
- `init`
|
||||
- `status`
|
||||
- `assistant`
|
||||
- `user`
|
||||
- `result`
|
||||
- fallback `stdout`
|
||||
|
||||
On failure results, set `isError=true` and include error text.
|
||||
|
||||
### Config builder (`src/ui/build-config.ts`)
|
||||
|
||||
- map `CreateConfigValues.url -> repository`
|
||||
- preserve env binding shape (`plain`/`secret_ref`)
|
||||
- include defaults (`pollIntervalSec`, `timeoutSec`, `graceSec`, `enableWebhooks`)
|
||||
|
||||
### Adapter fields (`ui/src/adapters/cursor-cloud/config-fields.tsx`)
|
||||
|
||||
Add controls for:
|
||||
|
||||
- repository
|
||||
- ref
|
||||
- model
|
||||
- autoCreatePr
|
||||
- branchName
|
||||
- poll interval
|
||||
- timeout/grace
|
||||
- paperclip public URL override
|
||||
- enable webhooks
|
||||
- env bindings for `CURSOR_API_KEY` and `CURSOR_WEBHOOK_SECRET`
|
||||
|
||||
### CLI formatter (`src/cli/format-event.ts`)
|
||||
|
||||
Format synthetic events similarly to local adapters.
|
||||
Highlight terminal failures clearly.
|
||||
|
||||
---
|
||||
|
||||
## Server Registration and Cross-Layer Contract Sync
|
||||
|
||||
### Adapter registration
|
||||
|
||||
- `server/src/adapters/registry.ts`
|
||||
- `ui/src/adapters/registry.ts`
|
||||
- `cli/src/adapters/registry.ts`
|
||||
|
||||
### Shared contract updates (required)
|
||||
|
||||
- add `cursor_cloud` to `packages/shared/src/constants.ts` (`AGENT_ADAPTER_TYPES`)
|
||||
- ensure validators accept it (`packages/shared/src/validators/agent.ts`)
|
||||
- update UI labels/maps where adapter names are enumerated, including:
|
||||
- `ui/src/components/agent-config-primitives.tsx`
|
||||
- `ui/src/components/AgentProperties.tsx`
|
||||
- `ui/src/pages/Agents.tsx`
|
||||
- consider onboarding wizard support for adapter selection (`ui/src/components/OnboardingWizard.tsx`)
|
||||
|
||||
Without these updates, create/edit flows will reject the new adapter even if package code exists.
|
||||
|
||||
---
|
||||
|
||||
## Cancellation Semantics
|
||||
|
||||
Long-polling HTTP adapters must support run cancellation.
|
||||
|
||||
V1 requirement:
|
||||
|
||||
- register a cancellation handler per running adapter invocation
|
||||
- `cancelRun` should invoke that handler (abort fetch/poll loop + optional Cursor stop call)
|
||||
|
||||
Current process-only cancellation maps are insufficient by themselves for Cursor.
|
||||
|
||||
---
|
||||
|
||||
## Comparison with `claude_local`
|
||||
|
||||
| Aspect | `claude_local` | `cursor_cloud` |
|
||||
|---|---|---|
|
||||
| Execution model | local subprocess | remote API |
|
||||
| Updates | stream-json stdout | webhook + polling + synthesized stdout |
|
||||
| Session id | Claude session id | Cursor agent id |
|
||||
| Skill delivery | local skill dir injection | authenticated fetch from Paperclip skill endpoints |
|
||||
| Paperclip auth | injected local run JWT env var | bootstrap token exchange -> run JWT |
|
||||
| Cancellation | OS signals | abort polling + Cursor stop endpoint |
|
||||
| Usage/cost | rich | not exposed by Cursor API |
|
||||
|
||||
---
|
||||
|
||||
## V1 Limitations
|
||||
|
||||
1. Cursor does not expose token/cost usage in API responses.
|
||||
2. Conversation stream is text-only (`user_message`/`assistant_message`).
|
||||
3. MCP/tool-call granularity is unavailable.
|
||||
4. Webhooks currently deliver status-change events, not full transcript deltas.
|
||||
|
||||
---
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. Reduce polling frequency further when webhook reliability is high.
|
||||
2. Attach image payloads from Paperclip context.
|
||||
3. Add richer PR metadata surfacing in Paperclip UI.
|
||||
4. Add webhook replay UI for debugging.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
### Adapter package
|
||||
|
||||
- [ ] `packages/adapters/cursor-cloud/package.json` exports wired
|
||||
- [ ] `packages/adapters/cursor-cloud/tsconfig.json`
|
||||
- [ ] `src/index.ts` metadata + configuration doc
|
||||
- [ ] `src/api.ts` bearer-auth client + typed errors
|
||||
- [ ] `src/server/execute.ts` hybrid webhook/poll orchestration
|
||||
- [ ] `src/server/parse.ts` stream parser + not-found detection
|
||||
- [ ] `src/server/test.ts` env diagnostics
|
||||
- [ ] `src/server/webhook.ts` signature verification + payload helpers
|
||||
- [ ] `src/server/index.ts` exports + session codec
|
||||
- [ ] `src/ui/parse-stdout.ts`
|
||||
- [ ] `src/ui/build-config.ts`
|
||||
- [ ] `src/ui/index.ts`
|
||||
- [ ] `src/cli/format-event.ts`
|
||||
- [ ] `src/cli/index.ts`
|
||||
|
||||
### App integration
|
||||
|
||||
- [ ] register adapter in server/ui/cli registries
|
||||
- [ ] add `cursor_cloud` to shared adapter constants/validators
|
||||
- [ ] add adapter labels in UI surfaces
|
||||
- [ ] add Cursor webhook route on server (`/api/adapters/cursor-cloud/webhooks`)
|
||||
- [ ] add auth exchange route (`/api/agent-auth/exchange`)
|
||||
- [ ] add skill serving routes (`/api/skills/index`, `/api/skills/:name`)
|
||||
- [ ] add generic cancellation hook for non-subprocess adapters
|
||||
|
||||
### Tests
|
||||
|
||||
- [ ] api client auth/error mapping
|
||||
- [ ] terminal status mapping (`FINISHED`, `ERROR`, unknown terminal)
|
||||
- [ ] session codec round-trip
|
||||
- [ ] config builder env binding handling
|
||||
- [ ] webhook signature verification + dedupe
|
||||
- [ ] bootstrap exchange happy path + expired/invalid token
|
||||
|
||||
### Verification
|
||||
|
||||
- [ ] `pnpm -r typecheck`
|
||||
- [ ] `pnpm test:run`
|
||||
- [ ] `pnpm build`
|
||||
|
||||
226
doc/plans/deployment-auth-mode-consolidation.md
Normal file
226
doc/plans/deployment-auth-mode-consolidation.md
Normal file
@@ -0,0 +1,226 @@
|
||||
# Deployment/Auth Mode Consolidation Plan
|
||||
|
||||
Status: Proposal
|
||||
Owner: Server + CLI + UI
|
||||
Date: 2026-02-23
|
||||
|
||||
## Goal
|
||||
|
||||
Keep Paperclip low-friction while making the mode model simpler and safer:
|
||||
|
||||
1. `local_trusted` remains the default and easiest path.
|
||||
2. one authenticated runtime mode supports both private-network local use and public cloud use.
|
||||
3. onboarding/configure/doctor stay primarily interactive and flagless.
|
||||
4. Board identity is represented by a real user row in the database, with explicit role/membership integration points.
|
||||
|
||||
## Product Constraints (From Review)
|
||||
|
||||
1. `onboard` default flow is interactive (no flags required).
|
||||
2. first mode choice defaults to `local_trusted`, with clear UX copy.
|
||||
3. authenticated flow gives guidance for private vs public exposure.
|
||||
4. `doctor` should also be flagless by default (read config and evaluate the selected mode/profile).
|
||||
5. do not add backward-compatibility alias layers for abandoned mode names.
|
||||
6. plan must explicitly cover how users/Board are represented in DB and how that affects task assignment and permissions.
|
||||
|
||||
## Current Implementation Audit (As Of 2026-02-23)
|
||||
|
||||
## Runtime/Auth
|
||||
|
||||
- Runtime deployment modes are currently `local_trusted | cloud_hosted` (`packages/shared/src/constants.ts`).
|
||||
- `local_trusted` actor is currently synthetic:
|
||||
- `req.actor = { type: "board", userId: "local-board", source: "local_implicit" }` (`server/src/middleware/auth.ts`).
|
||||
- this is not a real auth user row by default.
|
||||
- `cloud_hosted` uses Better Auth sessions and `authUsers` rows (`server/src/auth/better-auth.ts`, `packages/db/src/schema/auth.ts`).
|
||||
|
||||
## Bootstrap/Admin
|
||||
|
||||
- `cloud_hosted` requires `BETTER_AUTH_SECRET` and reports bootstrap status from `instance_user_roles` (`server/src/index.ts`, `server/src/routes/health.ts`).
|
||||
- bootstrap invite acceptance promotes the signed-in user to `instance_admin` (`server/src/routes/access.ts`, `server/src/services/access.ts`).
|
||||
|
||||
## Membership/Assignment Integration
|
||||
|
||||
- User task assignment requires active `company_memberships` entry for that user (`server/src/services/issues.ts`).
|
||||
- Local implicit board identity is not automatically a real membership principal; this is a gap for “board as assignable user” semantics.
|
||||
|
||||
## Proposed Runtime Model
|
||||
|
||||
## Modes
|
||||
|
||||
1. `local_trusted`
|
||||
- no login required
|
||||
- localhost/loopback only
|
||||
- optimized for single-operator local setup
|
||||
|
||||
2. `authenticated`
|
||||
- login required for human actions
|
||||
- same auth stack for both private and public deployments
|
||||
|
||||
## Exposure Policy (Within `authenticated`)
|
||||
|
||||
1. `private`
|
||||
- private-network deployments (LAN, VPN, Tailscale)
|
||||
- low-friction URL handling (`auto` base URL)
|
||||
- strict host allow policy for private targets
|
||||
|
||||
2. `public`
|
||||
- internet-facing deployments
|
||||
- explicit public base URL required
|
||||
- stricter deployment checks in doctor
|
||||
|
||||
This is one authenticated mode with two safety policies, not two different auth systems.
|
||||
|
||||
## UX Contract
|
||||
|
||||
## Onboard (Primary Path: Interactive)
|
||||
|
||||
Default command remains:
|
||||
|
||||
```sh
|
||||
pnpm paperclip onboard
|
||||
```
|
||||
|
||||
Interactive server step:
|
||||
|
||||
1. ask mode with default selection `local_trusted`
|
||||
2. copy for options:
|
||||
- `local_trusted`: "Easiest for local setup (no login, localhost-only)"
|
||||
- `authenticated`: "Login required; use for private network or public hosting"
|
||||
3. if `authenticated`, ask exposure:
|
||||
- `private`: "Private network access (for example Tailscale), lower setup friction"
|
||||
- `public`: "Internet-facing deployment, stricter security requirements"
|
||||
4. only if `authenticated + public`, ask for explicit public URL
|
||||
|
||||
Flags are optional power-user overrides, not required for normal setup.
|
||||
|
||||
## Configure
|
||||
|
||||
Default command remains interactive:
|
||||
|
||||
```sh
|
||||
pnpm paperclip configure --section server
|
||||
```
|
||||
|
||||
Same mode/exposure questions and defaults as onboarding.
|
||||
|
||||
## Doctor
|
||||
|
||||
Default command remains flagless:
|
||||
|
||||
```sh
|
||||
pnpm paperclip doctor
|
||||
```
|
||||
|
||||
Doctor reads configured mode/exposure and applies relevant checks.
|
||||
Optional flags may exist for override/testing, but are not required for normal operation.
|
||||
|
||||
## Board/User Data Model Integration (Required)
|
||||
|
||||
## Requirement
|
||||
|
||||
Board must be a real DB user principal so user-centric features (task assignment, membership, audit identity) work consistently.
|
||||
|
||||
## Target Behavior
|
||||
|
||||
1. `local_trusted`
|
||||
- seed/ensure a deterministic local board user row in `authUsers` during setup/startup.
|
||||
- actor middleware uses that real user id instead of synthetic-only identity.
|
||||
- ensure:
|
||||
- `instance_user_roles` includes `instance_admin` for this user.
|
||||
- company membership can be created/maintained for this user where needed.
|
||||
|
||||
2. `authenticated`
|
||||
- Better Auth sign-up creates user row.
|
||||
- bootstrap/admin flow promotes that real user to `instance_admin`.
|
||||
- first company creation flow should ensure creator membership is active.
|
||||
|
||||
## Why This Matters
|
||||
|
||||
- `assigneeUserId` validation checks company membership.
|
||||
- without a real board user + membership path, assigning tasks to board user is inconsistent.
|
||||
|
||||
## Configuration Contract (Target)
|
||||
|
||||
- `server.mode`: `local_trusted | authenticated`
|
||||
- `server.exposure`: `private | public` (required when mode is `authenticated`)
|
||||
- `auth.baseUrlMode`: `auto | explicit`
|
||||
- `auth.publicBaseUrl`: required when `authenticated + public`
|
||||
|
||||
No compatibility aliases for discarded naming variants.
|
||||
|
||||
## No Backward-Compatibility Layer
|
||||
|
||||
This change is a clean cut:
|
||||
|
||||
- remove use of old split terminology in code and prompts.
|
||||
- config schema uses only canonical fields/values above.
|
||||
- existing dev instances can rerun onboarding or update config once.
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
## Phase 1: Shared Schema + Config Surface
|
||||
|
||||
- `packages/shared/src/constants.ts`: define canonical mode/exposure constants.
|
||||
- `packages/shared/src/config-schema.ts`: add mode/exposure/auth URL fields.
|
||||
- `server/src/config.ts` and CLI config types: consume canonical fields only.
|
||||
|
||||
## Phase 2: CLI Interactive UX
|
||||
|
||||
- `cli/src/prompts/server.ts`: implement defaulted mode prompt and authenticated exposure guidance copy.
|
||||
- `cli/src/commands/onboard.ts`: keep interactive-first flow; optional overrides only.
|
||||
- `cli/src/commands/configure.ts`: same behavior for server section.
|
||||
- `cli/src/commands/doctor.ts`: mode-aware checks from config, flagless default flow.
|
||||
|
||||
## Phase 3: Runtime/Auth Policy
|
||||
|
||||
- `server/src/index.ts`: enforce mode-specific startup constraints.
|
||||
- `server/src/auth/better-auth.ts`: implement `auto` vs `explicit` base URL behavior.
|
||||
- host/origin trust helper for `authenticated + private`.
|
||||
|
||||
## Phase 4: Board Principal Integration
|
||||
|
||||
- add ensure-board-user startup/setup step:
|
||||
- real local board user row
|
||||
- instance admin role row
|
||||
- ensure first-company creation path grants creator membership.
|
||||
- remove synthetic-only assumptions where they break user assignment/membership semantics.
|
||||
|
||||
## Phase 5: UI + Docs
|
||||
|
||||
- update UI labels/help text around mode and exposure guidance.
|
||||
- update docs:
|
||||
- `doc/DEPLOYMENT-MODES.md`
|
||||
- `doc/DEVELOPING.md`
|
||||
- `doc/CLI.md`
|
||||
- `doc/SPEC-implementation.md`
|
||||
|
||||
## Test Plan
|
||||
|
||||
- config schema tests for canonical mode/exposure/auth fields.
|
||||
- CLI prompt tests for default interactive selections and copy.
|
||||
- doctor tests by mode/exposure.
|
||||
- runtime tests:
|
||||
- authenticated/private works without explicit URL
|
||||
- authenticated/public requires explicit URL
|
||||
- private host policy rejects untrusted hosts
|
||||
- Board principal tests:
|
||||
- local_trusted board user exists as real DB user
|
||||
- board can be assigned tasks via `assigneeUserId` after membership setup
|
||||
- creator membership behavior for authenticated flows
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. `pnpm paperclip onboard` is interactive-first and defaults to `local_trusted`.
|
||||
2. authenticated mode is one runtime mode with `private/public` exposure guidance.
|
||||
3. `pnpm paperclip doctor` works flagless with mode-aware checks.
|
||||
4. no extra compatibility aliases for dropped naming variants.
|
||||
5. Board identity is represented by real DB user/role/membership integration points, enabling consistent task assignment and permission behavior.
|
||||
|
||||
## Verification Gate
|
||||
|
||||
Before merge:
|
||||
|
||||
```sh
|
||||
pnpm -r typecheck
|
||||
pnpm test:run
|
||||
pnpm build
|
||||
```
|
||||
Reference in New Issue
Block a user