Imported from json-choi/dopedb (
workspace-cloud/AGENTS.md). Install upstream withnpx skills add json-choi/dopedb --skill workspace-cloud. Copyright stays with the author.
workspace-cloud
Purpose
Workspace Web: the authenticated web and API control plane for DopeDB
workspaces, deployed as its own Next.js 16 (App Router) application built with
@opennextjs/cloudflare onto Cloudflare Workers (app.dopedb.dev), separate
from the Tauri desktop app in ../src/../src-tauri and from the marketing
../site. It is a distinct trust boundary: Cloudflare D1 (via the WORKSPACE_DB
binding declared in wrangler.jsonc) is the live store for workspace identity,
membership, audit metadata, secret-free shared connection templates, and
encrypted provider-integration material. Per the product's shared-access axis
(root AGENTS.md/CLAUDE.md), this app must never store a member-local
database password, an ordinary query result row, or any other long-lived
secret in a shared record; managed access is issued as short-lived,
member-specific credentials at request time. Workspace administration (members,
DB access, providers, backups and deletion, account sessions) is a DopeDB Desktop
Settings surface (../src/features/workspaceAdmin/, ADR
../docs/adr/0009-desktop-workspace-administration.md) that calls this app's
/api/v1 routes with the account's Bearer session; the browser keeps only sign-in,
Desktop approval, invitation acceptance, provider authorization start/complete
pages, public Articles, and a settings-moved notice. A companion Cloudflare Worker in
infrastructure/ (a separate deploy target) mints the short-lived Workload
Identity Federation tokens this app uses to reach GCP;
it is not part of the Next.js build.
Key Files
| File | Description |
|---|---|
package.json |
App name dopedb-workspace-cloud; Next.js 16, React 19, Drizzle ORM 0.45, better-auth, @neondatabase/serverless, sanitize-html; devDependencies include wrangler, @opennextjs/cloudflare, drizzle-kit, vitest, miniflare. Scripts cover dev/build/start, D1 migration (db:*) and contract tests, Cloudflare build/deploy (build:cloudflare, preview:cloudflare, deploy:cloudflare), and the separate identity Worker (build:identity, deploy:identity). |
next.config.mjs |
Sets outputFileTracingRoot/turbopack.root to the repo root, rewrites /ko and /ko/:path* to the locale-independent path, and emits a strict Content-Security-Policy plus HSTS/X-Frame-Options: DENY/Referrer-Policy: no-referrer on every response. |
middleware.ts |
Derives the request locale from the path, stores it in a one-year cookie, forwards it as a header for server components, and forces cache-control: private, no-store on /analyses/*, /article-invitations/*, and /open-article/* (revocable/identity-bearing pages that must never be cached). |
wrangler.jsonc |
Deploy config for the main Worker dopedb-workspace (custom domain app.dopedb.dev, no workers_dev/preview_urls). Declares the WORKSPACE_DB D1 binding (migrations_dir: "d1-migrations"), a service binding to this same Worker's identity companion (WORKLOAD_IDENTITY → dopedb-workspace-identity, entrypoint WorkspaceIdentity), and the fixed OIDC vars consumed by infrastructure/. |
wrangler.identity.jsonc |
Deploy config for the separate dopedb-workspace-identity Worker built from infrastructure/ — see infrastructure/AGENTS.md. |
open-next.config.ts |
defineCloudflareConfig() with no overrides; the actual Cloudflare adapter entry point for build:cloudflare/preview:cloudflare/deploy:cloudflare. |
drizzle.d1.config.ts |
drizzle-kit config for the live D1 schema (dialect: "sqlite", schema ./lib/d1/schema/index.ts, out ./d1-migrations) — the target of pnpm db:generate/db:check; see d1-migrations/AGENTS.md. |
tsconfig.json |
Strict Next.js TS project (ES2022, bundler resolution, @/* path alias to this directory); explicitly excludes identity-env.d.ts and infrastructure/identity-worker.ts, which build under infrastructure/tsconfig.identity.json instead. |
postcss.config.mjs |
Tailwind v4 via @tailwindcss/postcss only. |
vitest.contracts.config.ts |
Runs lib/control-plane-contracts.harness.ts and lib/d1-storage.harness.ts under pnpm test:contracts (repo root alias: pnpm check:knowledge). Aliases server-only to node:fs so server-only modules import cleanly under Node/Vitest. |
cloudflare-env.d.ts |
Wrangler-generated ambient CloudflareEnv bindings for the main Worker (regenerate with pnpm cf:typegen, never hand-edit). |
cloudflare-bindings.d.ts |
Hand-written narrow re-exports of Fetcher/Service/WorkerVersionMetadata/D1Database types from @cloudflare/workers-types, kept separate so importing them doesn't shadow the browser DOM's Request/Response globals used elsewhere in the app. |
identity-env.d.ts |
Wrangler-generated ambient IdentityEnv bindings for the separate identity Worker (regenerate per infrastructure/AGENTS.md, never hand-edit). |
README.md |
Local-setup and product-behavior notes: this app is a separate trust boundary from the Tauri desktop, the WORKSPACE_DB D1 binding is used directly (no Postgres connection string/emulation in production). |
.env.example |
Documented local environment variables (Google OAuth client, BETTER_AUTH_SECRET/URL, WORKSPACE_CREDENTIAL_KEY, Workspace KMS key/WIF settings, CRON_SECRET, background scheduler settings, GitHub Knowledge App settings) with placeholder, non-functional values only. |
.gitignore |
Ignores .open-next/, .wrangler/, and .dev.vars* (local Wrangler secrets). |
pnpm-workspace.yaml |
pnpm settings for this sub-project: allowBuilds for esbuild/workerd, pinned overrides (postcss, sharp, a nanoid floor, an esbuild-kit transitive pin), and a 1440-minute (minimumReleaseAgeStrict) minimum package-release age gate. |
pnpm-lock.yaml |
Lockfile for this sub-project's own dependency graph. |
next-env.d.ts |
Next.js-generated ambient type reference (.next/types/routes.d.ts, .next/types/root-params.d.ts); regenerated automatically, never hand-edited. |
Subdirectories
| Directory | Purpose |
|---|---|
app/ |
Next.js App Router tree: every route.ts/page.tsx, plus their screen-local components (see app/AGENTS.md). |
lib/ |
Server-only core: auth/session, workspace/connection/grant/lease domain logic, provider-integration adapters, Analysis Article storage, D1 schema, and the versioned HTTP contracts (see lib/AGENTS.md). |
features/ |
Analysis Article sharing handoff (articleSharing/): server-rendered invitation/open pages, one client accept button, and the server-only invitation SQL, composed by the article-handoff pages and sharing routes (see features/AGENTS.md). |
d1-migrations/ |
The live, production Cloudflare D1 migration set the deployed Worker actually reads and writes (see d1-migrations/AGENTS.md). |
infrastructure/ |
Source for the separate dopedb-workspace-identity Cloudflare Worker (GCP Workload Identity Federation token issuance) (see infrastructure/AGENTS.md). |
scripts/ |
D1 migration/deploy tooling and the isolated D1 migration/contract harness runner (see scripts/AGENTS.md). |
For AI Agents
Working In This Directory
- This is a separate pnpm project inside the repo (its own
pnpm-lock.yaml/pnpm-workspace.yaml); run its scripts withpnpm --dir workspace-cloud <script>or the repo-rootpnpm workspace:cloud:*aliases, not from the repo root directly. - D1 (
lib/d1/schema/, migrated viad1-migrations/) owns the current production schema. app/analyses/[slug]/PublicAnalysisArticle.tsximportsAnalysisArticleBodydirectly from the sibling Tauri desktop app's../../src/design-system/components/AnalysisArticleBody.tsx(also referenced by a Tailwind@sourcescan inapp/globals.css) — this is a real, intentional cross-project dependency for the shared Analysis Article rendering primitive, not an accident; keep it in sync with the desktopsrc/design-system/component if either changes.- Public Analysis Article publications are immutable HTML snapshots that never execute the saved query at read time (see
docs/adr/0007-analysis-article-bi-domain.mdat the repo root);middleware.tsadditionally forcescache-control: private, no-storeon/analyses/*,/article-invitations/*, and/open-article/*so a revoked publication or a private handoff link cannot be served from a shared cache. - Long-lived secrets are never written into a shared/workspace record; managed provider credentials are issued as short-lived, member-specific leases at request time (see
lib/AGENTS.md,lib/provider-integrations/AGENTS.md, and the connection-lease routes inapp/AGENTS.md). - Provider OAuth started from Desktop opens only this origin's
/auth/provider/startpage, which never consumes the state and continues to the provider only for the browser account that started it. The PlanetScale and GCP callbacks must keep checking the cookie session's user against the state's user; never relax them to trust the state alone (ADR 0009). - Follow the repo-root
AGENTS.md/CLAUDE.mdrule that connecting, reconnecting, importing, repairing, or issuing/revoking credentials must never rewrite a pre-existing application user, role membership, grant, default privilege, or ACL; this app's D1 schema only ever records DopeDB's own workspace-management state (seed1-migrations/AGENTS.md). - No provider API is ever called directly from application code outside the officially adapted flows this app owns (OAuth/API-key provider integrations for GCP Cloud SQL, Neon, PlanetScale, Vault); the AI-provider "official CLI only" rule in root
CLAUDE.mdis a separate boundary and does not apply to this sub-project's own database-provider integrations.
Testing Requirements
pnpm workspace:cloud:build(repo root) /pnpm --dir workspace-cloud build— Next.js build; the baseline check for any change here.pnpm --dir workspace-cloud test:contracts(repo-root aliaspnpm check:knowledge) — Vitest wire-contract/serialization suite (lib/control-plane-contracts.harness.ts,lib/d1-storage.harness.ts); does not exerciseapp/**route handlers directly.bash scripts/test-provider-import-d1.sh(repo root; execsworkspace-cloud/scripts/test-d1-migrations.mjs, equivalentlypnpm --dir workspace-cloud test:d1-import) — run for any change tod1-migrations/,scripts/migrate-d1.mjs, orscripts/migrate-production.sh; exercises the production migration entry point against an isolated, disposable local D1.pnpm workspace:cloud:verify-deployment <worker-version-id>(repo root, runsscripts/check-workspace-deployment.mjs) — required before reporting a deployment as successful; confirms the named Worker version is receiving 100% of production traffic and matches the live production-domain deployment receipt. A local build or test pass alone is never deployment evidence.pnpm lint(repo root;lint:hooks→eslint src workspace-cloud site --ext .ts,.tsx --max-warnings 0) lints this directory's.ts/.tsxfiles with zero warnings allowed, despite its script name.- Any UI change under
app/orfeatures/also needs a manual check per rootAGENTS.md/CLAUDE.md; there is no route-level HTTP integration test suite forapp/api/**in this repository.
Common Patterns
- Server-only data/session logic lives in
lib/;app/**/route.tsandpage.tsxfiles are thin HTTP/RSC boundaries over it, and page-spanning feature modules (currently onlyarticleSharing/) live infeatures/. Seeapp/AGENTS.mdfor the six-step mutating-route pattern (origin check → capability authorization → optimistic-revision check → scoped DB lookup → body validation → versioned/gated commit) that mostroute.tsfiles follow. - Wire contracts are hand-validated at both the sender and receiver (strict parsers rejecting unexpected shape) rather than trusted from a shared type import alone — see the Desktop administration parsers under
../src/features/workspaceAdmin/(e.g.providers/neonBranches/branchInventory.ts) andlib/AGENTS.md's contract files.
Dependencies
Internal
../src/design-system/components/AnalysisArticleBody.tsx(see note above),WorkspaceIdentity.tsx(identity shell primitives for the auth, invitation, handoff, settings-moved, and error pages),DopeDBMarkGraphic.tsx(app/components/Brand.tsx), and../src/design-system/workspace.css(imported byapp/globals.css) — the cross-project imports from the Tauri desktop app's frontend.../docs/adr/0007-analysis-article-bi-domain.md— the ADR this app's Analysis Article sharing/publication model implements.../docs/adr/0009-desktop-workspace-administration.mdand../src/features/workspaceAdmin/— the decision that moved workspace administration out of this app, and the Desktop Settings client of its administration routes.../scripts/deploy-workspace-cloudflare.mjs,../scripts/check-workspace-deployment.mjs,../scripts/test-provider-import-d1.sh— repo-root automation that builds, deploys, and verifies this sub-project.
External
next16,react/react-dom19,drizzle-orm0.45 +drizzle-kit,better-auth+@better-auth/drizzle-adapter,@neondatabase/serverless,sanitize-html,pretendard(font).wrangler,@opennextjs/cloudflare,miniflare(devDependencies; Cloudflare build/deploy/local-runtime tooling).vitest(contract and provider-harness test runners).
