Salta ai contenuti

ADR 0020: the HTTP runtime, first-run setup and web deployment

Questi contenuti non sono ancora disponibili nella tua lingua.

  • Status: accepted; onboarding and integration mode superseded by ADR 0021
  • Date: 2026-10-26

server is the composition root. Only server/config/environment.ts reads the environment. Configuration is validated before connections open, and secret values have an explicit reveal() method. HTTP and migration entry points do nothing when imported. Next.js calls the HTTP runtime lazily on the first request.

The app connects as its dedicated runtime role, with a password derived from AUTH_SECRET and the instance identifier. The migrator uses the owner’s direct or session-pooler connection, creates the runtime roles, applies numbered SQL under the migration lock and reapplies inventory grants. Runtime schema checks use a generated list of migration checksums, so the web bundle does not read SQL files from disk. Regenerate that list with pnpm --filter @guestcraft/db migrations:revisions.

First-run setup requires an 80-bit code derived from the instance secret. The migration command prints it privately until setup finishes. The first user, organization, production workspace, audit entries and instance marker are written atomically. Once present, that marker prevents setup from running again, including concurrent attempts. The server never sends the code to a browser.

The deployed HTTP runtime keeps public sign-up closed. Knowing an invited email is not sufficient proof of possession. Token-based invitation onboarding is implemented (ADR 0030); email delivery remains future work. Organization creation is disabled by default; self-service requires explicit configuration. The first operator is created by the protected setup transaction, bypassing public sign-up.

The dashboard renders the active organization and permitted workspaces. Setup, login and errors use the English and Italian catalogs, negotiated from the browser or a supported language cookie. All document attributes are rendered on html before paint. UI modules carry their own client boundaries.

Docker Compose runs PostgreSQL 17, a one-shot migration service and Next.js with standalone output as a non-root user. Images are pinned by digest. Copy .env.example and generate separate secrets before docker compose up --build. The web binds to loopback by default. A remote deployment needs HTTPS and a trusted ingress; it must not expose owner credentials or trust client-supplied IP headers.

Vercel uploads must retain every workspace manifest referenced by the frozen lockfile. .vercelignore omits analysis artifacts but includes docs/analysis/package.json; a deploy dry run verifies packaging without exposing local environment files.

Vercel is an optional deployment target. The project root is apps/web, Node 24, and the build is pnpm --filter @guestcraft/web build. Neon Marketplace supplies pooled and direct URLs. Migration is an explicit deployment step, never a request side effect. Preview URLs run the same application as other hosted installations. Customers configure Channex per workspace during onboarding, as ADR 0021 describes.

The HTTP integration suite runs migration, setup, sign-in, health, OpenAPI, authorization and schema mismatch checks on PostgreSQL through PgBouncer. Web form tests cover Enter submission, failures, pending input and sign-out. Next.js builds without database secrets. A browser smoke test checks the rendered pages.

The initial SDK and Channex connection onboarding are described in ADR 0021. Operational booking screens, synchronization handlers and the server’s resident worker/cron entry points remain separate work. The job package is integrated with the same database migrations and unit of work; no job handlers are registered by the empty dashboard.

Persistence review correction (2026-10-28)

Section titled “Persistence review correction (2026-10-28)”

Database role derivation now accepts an independent GUESTCRAFT_DATABASE_ROLE_SECRET and GUESTCRAFT_DATABASE_ROLE_ID. Legacy defaults remain available; existing passwords are preserved and candidate logins verified. Credential rotation stages a new role pair before promotion and retires the old pair only after drainage. ADR 0038 now permits older compatible runtimes to cold-start across expand-only migrations; contract revisions still require roll-forward. Runtimes built before that compatibility change retain strict journal equality. One session lock covers journal validation, SQL, role bootstrap, grants and job registration. SQL must succeed before bootstrap. See installation.

Section titled “Cookie-authenticated BFF origin boundary (28 October 2026)”

The HTTP composition always injects cookieOriginPolicy into the API’s shared cookieRequestAllowed boundary. Before authentication, body parsing or route handlers, all POST, PUT, PATCH and DELETE requests (including bodyless requests, team and identity routes) must declare an exact trusted Origin: the configured base URL’s origin or an explicit GUESTCRAFT_TRUSTED_ORIGINS entry. An untrusted or opaque (null) Origin is refused even when Fetch Metadata claims same-origin. Without Origin, only Sec-Fetch-Site: same-origin or none is accepted; missing, same-site, cross-site and unknown values are refused. GET, HEAD and OPTIONS remain subject to their ordinary route rules. Denial uses the localized RFC 9457 request.untrusted_origin problem (403). The composition logs only the refused origin, shortened to 128 characters with any path, query, credentials and control characters removed. Sign-in explains this code. Never infer trust from Host, Forwarded, Referer, cookies, JSON content types or CORS. Non-browser cookie clients must send a configured Origin on writes.

The independently authenticated /api/channex/signal/:org/:ws webhook and GET /api/internal/channex/drain cron are mounted outside this API boundary: respectively their per-workspace secret and cron bearer secret authorize them, never the session cookie. First-run setup is a Next.js server action with Next.js origin checks plus the private setup code and single-use instance marker. Other server actions retain Next.js’s origin checks. These are narrow composition boundaries, not path-based exemptions within the cookie API. Better Auth retains its additional identity-specific CSRF protections. The team gateway also applies the same origin policy when embedded directly outside the HTTP composition.

Regression evidence includes a production-build Playwright page on a second, same-site localhost origin issuing a credentialed no-cors bodyless drain request. It asserts a 403, actual session-cookie delivery and no new database audit entry, with trusted browser, header-fallback and persisted JSON-write controls. Existing real-database integration tests verify setup, webhook secrets and cron bearer auth.

The Next.js 16 request proxy sets a fresh nonce CSP on each response and forwards the same policy to dynamic page rendering, which lets Next.js nonce its own scripts. Every route class receives frame denial and common browser security headers; the API reference retains its narrower nonce CSP. Authenticated and internal responses are not cached, while immutable static assets retain their cache behavior. An instance whose configured GUESTCRAFT_BASE_URL is HTTPS (or that runs on Vercel) sends HSTS without includeSubDomains, because an operator may use other subdomains; the decision never reads a request’s X-Forwarded-Proto, which any client can set. The API reference keeps its own no-referrer policy. Local loopback HTTP permits development tooling’s eval; hosted origins do not.