Files
paperclip/doc/plans/2026-02-23-deployment-auth-mode-consolidation.md
Dotta 9c7d9ded1e docs: organize plans into doc/plans with date prefixes
Move plans from doc/plan/ into doc/plans/ and add YYYY-MM-DD date
prefixes to all undated plan files based on document headers or
earliest git commit dates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 09:11:56 -05:00

7.9 KiB

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
  1. 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
  1. 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:

pnpm paperclipai 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"
  1. if authenticated, ask exposure:
  • private: "Private network access (for example Tailscale), lower setup friction"
  • public: "Internet-facing deployment, stricter security requirements"
  1. 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:

pnpm paperclipai configure --section server

Same mode/exposure questions and defaults as onboarding.

Doctor

Default command remains flagless:

pnpm paperclipai 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.
  1. 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 paperclipai onboard is interactive-first and defaults to local_trusted.
  2. authenticated mode is one runtime mode with private/public exposure guidance.
  3. pnpm paperclipai 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:

pnpm -r typecheck
pnpm test:run
pnpm build