# Operating Spool

Use Node 22 or later and git, on macOS, Linux or Windows with WSL2. Start it with one line, `npx -y @runeditrun/rer dev just-every/run-spool` (set up only: `npx -y @runeditrun/rer setup just-every/run-spool`; without Node: `curl -fsSL https://runeditrun.com/install.sh | sh -s -- dev just-every/run-spool`), as on the Run page. Dev installs dependencies and prepares the owner when needed; `rer install` and `rer setup` remain available separately. Spool pins rer 0.2.22; rer supplies the declared Node 22.20.0 and pnpm 10.12.4 runtimes. The exact public commands and flags are in [SEED-COMMAND-CONTRACT.md](SEED-COMMAND-CONTRACT.md); [README.md](README.md) gives the complete local journey. `rer check --profile first-party` validates the portable contract. `rer doctor` separately checks tools and configured inputs. Neither proves live provider access.

Local setup uses one `rer-setup.json` manifest and Base lifecycle. Private settings stay in the ignored 0600 `.dev.vars`; state directories are 0700. Setup preserves the existing owner and refuses a conflicting email. Local development opens the owner through the private one-click `RER_OPEN` link and silently suppresses mail. Hosted installations use native email confirmation or a configured Auth installation. Owner sessions and same-origin checks protect private APIs and writes.

The native dependency build allowlist contains only esbuild and workerd. Push and pull-request Verify runs have a five-minute build/check job followed by eight parallel test shards, each limited to ten minutes; core project erasure, the full-cap project erasure proof and owner erasure each have a dedicated shard. They reuse only the emitted build and capture helper, preserve every non-command `verify:app` suite, and check hosted output and hydration. Browser jobs install Playwright's bundled Chromium with system dependencies and installation diagnostics enabled. The separate Command journey workflow runs the public CLI installer/README clone journey nightly at 15:17 UTC, on release tag pushes and on manual dispatch, with a 90-minute limit. Local `pnpm verify` still runs the complete verification including that journey.

`rer dev` and `rer preview` are managed servers with health checks. Dev stays in the foreground with TTY output and detaches once ready with non-TTY output; `--foreground` and `--detach` select explicitly. Preview serves fixed production output. Both use `.wrangler/state`; `rer demo` uses disposable `.rer/demo` state; alternate requested ports override runtime origin only. Stop them with their matching `--stop` command before changing mode, reset or transfer. Private APIs and documents recheck the active persisted owner. The exceptions are public pages without an owner session: `/` serves the static landing page (no script, its own CSP) with a Sign in link, and `/docs` serves the docs built from the repository Markdown that `docs/site.json` lists; owners keep the workbench at `/`, and every other document still redirects to `/sign-in`. A project ingest or agent key cannot authorize installation operations. Origin/CSRF checks use configured AUTH_ORIGIN, never a forwarded host. Open the private `RER_OPEN` link printed by `rer dev` to enter as the owner. Its `rer-dev` fragment value gates owner sessions and dev agent reads; never share it or put it in a browser app. Dev and demo silently suppress mail and expose no mailbox viewer.

With rer dev v2 (pinned rer 0.2.22), dev-minted ingest and deploy keys work against the local entry from an external app or build without the owner gate. Ingest only sends events to its project; deploy only registers releases and uploads maps for its project. Allow the browser application's exact origin in Capture. Read-only dev agent keys still need the gate: privately extract only the `rer-dev` fragment value as `SPOOL_DEV_KEY` for trusted local CLI/MCP use, with the CLI invoked by its absolute path in the Spool checkout when working from an app repository. Deployed Spool refuses every dev-minted grant. Sign in there and create fresh keys; private archives exclude all project grants. See [key scopes](docs/capture.md#keys-for-anything-outside-this-checkout) and [private agent setup](docs/WITH-YOUR-AGENT.md#local-agent-access).

For non-interactive setup, put a JSON object such as `{"OWNER_EMAIL":"you@example.com"}` in the ignored `.rer/local/setup-answers.json` (owned regular file, mode `0600`, no shared hard links). Run `rer setup --answers .rer/local/setup-answers.json --non-interactive` from the Spool checkout. The path must be relative and contained inside it; absolute paths, traversal and symlinks are refused. Saved answers are reused. Keep addresses and secrets out of command arguments; see [installation](docs/install.md#install-and-start).

## Existing installation recovery

Existing SQL filenames and bytes in `migrations/core` are unchanged; migrations 0004 onward are additive. Preserve the `d1_migrations` ledger. Old password sessions are inert; existing project grants, projects, maps, events, origins and audit rows remain domain data. Do not use a domain archive as an in-place migration backup: archives deliberately exclude credentials.

The former placeholder D1 identity maps to a different local SQLite resource than the current explicitly local `errors-local` identity. Setup refuses a legacy domain store without operator-auth, multiple candidate databases, linked storage, or generated-output state directories before building, rewriting secrets or migrating. It does not pick the newest store or merge databases. An interrupted partial SQL transition also requires inspecting its ledger before resuming.

Setup and archive preflight inspect a private database/WAL copy. Ordinary checkpoint/reuse tails are accepted after validating the current committed WAL generation and recognizing older reset generations; current checksum damage, incomplete frames and ambiguous generation boundaries still require explicit recovery. Inspection never checkpoints or truncates the original state. Keep the database and its WAL together when preserving an offline copy: committed owner and project changes may exist only in the WAL.

To recover deliberately, first stop every runtime and retain a complete private copy of each candidate state directory, its original Wrangler configuration and private settings. Work on a separate destination, leaving originals intact. With the original configuration and its explicitly selected `--persist-to` directory, use Wrangler's local D1 export to make a full SQL backup including credentials and migration ledger. Create a fresh canonical destination using the current local binding; import the full SQL backup with local D1 execute, preserving the original filenames/ledger, then apply only unapplied migrations from `migrations/core`. Confirm project, credential, event, map and origin counts and grant behavior in that destination before selecting it as the active checkout. Resolve or relocate every obsolete state path only after this explicit comparison. Never delete a candidate to make preflight pass.

After the recovered destination has the additive auth tables, run setup with the intended first owner email. It creates the first real owner; subsequent setup preserves it. The managed-secret helper adds missing auth settings while preserving existing secret values. Only after successful bootstrap/preservation and local owner entry does Spool retire the old OWNER_PASSWORD setting and `.local/owner-password.txt`. An interrupted attempt retains recoverable private state. Never copy a password session into operator-auth.

## Data movement and retention

Use `rer export --target local --output exports/spool.jsonl` with the app stopped. Base portability inventories all owned D1 tables, snapshots them consistently, and writes a new private archive. Authentication/owner tables, credential grants, legacy password sessions, development outbox, admission guards, portability bookkeeping and migration metadata are excluded. Private maps and browser-origin configuration are included. Configuration names, never values, are listed for recovery.

For browser restore, start a fresh installation and sign in as its owner. On `/setup`, choose **Restore a private archive** before creating a project. Select the archive in the existing **Archive and restore** form, review its preview and choose **Restore into empty installation**. The same form is in Settings on completed installations. Creating a temporary project makes the destination nonempty and prevents restore. Browser uploads are limited to 16 MiB; use the local CLI for larger Base archives.

For CLI restore, stop the destination runtime and use an empty domain database. A browser-downloaded archive must be a regular file owned by you with private permissions (0600). On macOS, Linux or WSL2, secure it before import (replace the example path with your downloaded file):

```sh
chmod 600 /path/to/spool-private-archive.jsonl
rer import --target local --input /path/to/spool-private-archive.jsonl --yes
```

For a CLI-exported archive, use `rer import --target local --input exports/spool.jsonl --yes`. Both CLI and owner HTTP restore import the same semantic policy: normalized scrubbed events, map integrity, grouping, references, audit shapes and capacities. New JSONL content, part integrity and empty-target/atomic commit are owned by Base. Strict native v1/v2 decoding translates into the same Base staging and commit; there is no legacy SQL restore loop. Restore creates neither grants nor an owner. Run setup if the destination has no owner and create new project grants afterward. An already established destination owner is preserved. Unsupported remote transfer fails explicitly. Native v1/v2 archives have a 16 MiB total input limit and a 10,000-row limit per table. Base portability JSONL instead limits each line to 24 MiB, each part to 16 MiB and each part to 1,000 rows; it has no equivalent total archive or per-table row limit. Both formats enforce the domain capacities, including 10,000 retained events, 20 maps and 20 explicit browser origins per project.

Owner HTTP transfer uses `/api/archive` and `/api/restore`; restore additionally requires `X-Confirm-Restore: yes` and same-origin owner authority. D1 triggers block domain writers while a restore is staged; Base removes and restores those triggers inside the atomic commit. Failed imports abort their staging. A process crash can leave a staged import reserved; preserve its state and inspect Base's import status/abort API before retrying. There is no silent takeover of an incomplete destructive operation.

Retention remains manual preview/apply. Its transaction removes selected event occurrences and retry identities, updates derived headlines, removes unused old map occurrences, records erasure tombstones and invalidates stored snapshots/staging. Journal writes and deletion recheck the same occurrence and current map references in that transaction. Event IDs and map keys can be accepted again with new occurrence identities; their new archives retain every older tombstone without fencing the new evidence. The apply result and audit report the number actually deleted, which can differ from an earlier preview under concurrent writes.

The additive occurrence migration preserves existing event/map bytes, grants and the migration ledger. Pre-transition rows and native v1/v2 imports carry a legacy occurrence marker; pre-transition Base JSONL archives upgrade through Base staging. Legacy occurrences remain subject to both the original natural-key tombstones and the new occurrence-key tombstones. An already ambiguous legacy row whose natural key was tombstoned remains fenced; migration does not infer whether it was reaccepted or erase journal history. Historical comparisons across the cutoff are incomplete. An owner-held exported file remains outside live retention; old legacy files cannot invent later erasure history.

Reset requires `--yes`, refuses active or unsafe state, and displays its exact inventory: `.wrangler/state`, `.rer/local`, `.seed/installer`, `.dev.vars` and `.dev.vars.previous` when present. It preserves source, dependencies, exports and `.rer/deploy` receipts. The next setup creates a fresh empty installation and owner.

Project erasure under BASE-DATA-003 is implemented: Settings → Erase this project (or `GET`/`POST /api/projects/:project/erasure`, owner session, same origin) shows a count-only preview, requires the typed project identifier and returns a receipt with counts and times. It deletes the project and everything recorded for it in one fenced cascade (see docs/PROJECT-ERASURE.md), and a Sentry-imported project's import custody and original export bytes in the same import-kit batch. Do not treat retention as project erasure. Take a private archive first if you may need the data: an archive made before the erasure is refused by this installation afterwards. Scope exceptions and external-copy limits are in [CONTRACT.md](CONTRACT.md).

## Owner-account deployment

### Sentry intake, artifacts and historical import

The implemented [switching guide](docs/SENTRY-SWITCHING.md) covers project-scoped ingest DSNs for supported JavaScript exception envelope/store intake, required release/build registration and exact browser origins. Selected exception fields and breadcrumb labels use the native scrubber; unsupported items are counted and discarded. Unmodified `@sentry/browser` and `@sentry/node` 11.6.0 verification programs exist; their actual results are recorded separately, and dependency pins alone prove no runtime behavior. The source-map CLI/Rollup/Vite plugin uses a server-side project deploy key for release registration and bounded flat map upload only. It does not upload Sentry artifact bundles or authorize ingest, evidence reads or administration. Explicit breadcrumb labels use the [Spool SDK](docs/capture.md#1-add-the-spool-sdk).

Organization import is a separate owner-session operation through Base import-kit: inspect the scrubbed dry-run plan and omissions, apply the reviewed fingerprint, inspect/resume the journal and use guarded undo if needed. Only synthetic fixtures qualify the importer so far; no real export migration or feature-parity claim is accepted. Private source originals remain in the import journal, outside event retention and native archives; follow the guide's originals custody instructions before moving installations. The full verify includes these native intake, SDK, tooling and import programs; their exact-head results belong in the completion evidence, independently of hosted/provider acceptance.

### Deployment inputs

`rer deploy --dry-run` performs the real local build, packages emitted Worker/assets plus every `migrations/core` SQL file and validates `deploy.json`. It does not contact a provider. Actual deployment requires explicit authorization, CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN for an account with a workers.dev subdomain. Deploy-kit finds or creates its own declared D1 resources and generates real cloud IDs. `.rer/deploy` records resource ownership; unrelated existing resources are not adopted.

Provide OWNER_EMAIL, SESSION_SECRET, SETUP_TOKEN, MAIL_FROM, MAIL_API_KEY, ALERT_DESTINATIONS and ALERT_DESTINATION_HASH_KEY through the owner's approved secret source; [docs/HOSTED.md](docs/HOSTED.md) generates a complete private settings file (empty alert destinations, generated secrets and hash key), uses public rer to claim the hosted owner during `rer deploy` and retire the deployed `SETUP_TOKEN`, and gives the update and removal recipes. Preserve the original deploying checkout and its private `.rer/deploy/<environment>.json` ownership record/receipt for the deployment's lifetime. `rer deploy --remove --yes --environment production` removes only the resources recorded as created for that environment and their data; omit `--environment` only for the default `production`. A fresh clone without that record deletes nothing. Export needed evidence first. Local `rer reset` preserves deployment receipts and does not remove hosted resources. See [hosted removal](docs/HOSTED.md#remove-a-hosted-installation). The declaration selects SendGrid (`MAIL_PROVIDER=sendgrid`) and production AUTH_LOCAL=false; sender verification and mailbox delivery are owner-run acceptance work. AUTH_ORIGIN is derived by deploy-kit from the selected owner target. Health queries D1 and reports deploy-kit source/fingerprint/version metadata when configured. Hosted rollout, provider account actions, sender setup, receipts, public release and tags require separate owner authorization and evidence.

The descriptor's development-mail-outbox loop is a local Worker/D1 loop. Its hosted mail loop is disabled and optional until the owner configures delivery. Local completion must not be described as hosted mail acceptance.

## Managed mail

Managed Configure selects Base's `rer` mail adapter with `MAIL_PROVIDER=rer`, assigns `MAIL_FROM` and `MAIL_RELAY_URL`, and issues the private `MAIL_API_KEY` for the installation/environment on plan save. All four inputs are hidden. RER stores the credential in encrypted private settings; its mail provider key never reaches Spool or its build. No provider account or copied key is required from the owner. Free and Assisted self hosting retain their existing provider choices and sender configuration, including the manual SendGrid instructions in [docs/HOSTED.md](docs/HOSTED.md).

Base's relay adapter remains a byte-for-byte copy from rer 0.2.22. Its fetch dependency implements `redirect: 'error'` on Workers with a manual request and a rejected redirect response, because Workers accepts only `follow` and `manual`. Native sign-in's `Auto-Submitted` SMTP hint is omitted for the relay, which owns SMTP headers.

The relay allowance is shared by production and preview: 20 messages/minute, 200/hour and 1,000/day per installation. Valid attempts consume allowance even when the provider fails. A relay acceptance means provider acceptance, not recipient delivery. Mail errors use the normal queue/delivery policy. Rotation invalidates pending releases; build and publish again to bind the replacement credential. Disconnect or effective Managed exit revokes credentials; saving Configure alone cannot revive a revoked credential. Managed configuration support and local checks do not establish hosted deployment or live delivery.

## Optional managed admission and editor

[MANAGED-ADMISSION.md](MANAGED-ADMISSION.md) specifies the signed owner claim, trusted deployment bindings and optional editor. The additive `0011_owner_claim.sql` follows the applied auth ledger and preserves existing owner and evidence rows. Native local bootstrap remains available. Spool still has one owner; invitations and role management are disabled. A project agent grant never authorizes editor configuration.

The managed setup manifest hides its generated setup token. `rer-project.json` binds the committed canonical manifest bytes; the delivery producer and immutable managed workflow emit candidate build payloads only. `node scripts/build-delivery-artifact.mjs` requires a completed build and fresh output directory. The operations check validates source and generated local installation inputs; it does not establish live provider or hosted installation acceptance. Keep the setup digest and producer workflow synchronized with the canonical manifest and product-owned packaging policy. Artifact generation, clean-history export and the latest Base publish-check without exclusions need their own completion evidence.

## Per-project alerts

New-issue and later-release regression alerts are sent right after ingest: the admitting request expands that event's routed new-issue or regression intents and dispatches their mail and webhook deliveries through `waitUntil`, so they do not wait for the cron. The Worker cron runs every minute and evaluates complete server-received volume-window spikes, then recovers and dispatches persisted delivery jobs. Alerts that still depend on the cron: **spike** alerts (evaluated only there), **retries** of a rate-limited or failed delivery, deliveries left **queued** when the dispatch after ingest did not finish (an expired claim, a request that ended early, missing configuration fixed later), and expired-claim recovery. Every send takes a durable claim first, so the dispatch after ingest and a cron run that reach the same delivery send it once. Each scheduled run writes its time to `alert_scheduler.last_run`, idle or not; `/health` shows it as `alerts.lastScheduledRun` (null until the first run) and Settings shows it under **Scheduled alert run**, so a cron that ticks on an idle installation is told apart from one that never runs. Owners manage per-project rules for new issues, later-release regressions and spikes and inspect feed/delivery logs in Alerts. Feed transition payloads preserve the accepted release identity for later triage; retention cannot manufacture a new issue from a zero retained count. This is bounded source behavior; provider delivery and trusted egress remain separately qualified, with no Oncall dependency or Slack integration.

Owners add, edit and test email destinations directly in **Alerts → Rules**; follow the [page setup steps](docs/ALERT-WEBHOOKS.md#manage-email-destinations). These project-scoped addresses are private installation configuration and are excluded from archives. Legacy mail destinations and signed webhooks can still use server-only `ALERT_DESTINATIONS`. Keep `ALERT_DESTINATION_HASH_KEY` configured; issue links use the configured HTTPS `AUTH_ORIGIN` unless a trusted HTTPS `ALERT_PUBLIC_ORIGIN` overrides it. `rer-setup.json` declares both alert settings: the hash key is a generated hidden secret (setup, `rer deploy` and Managed Configure each create one when it is missing and keep an existing one), and the destinations are an optional owner-entered secret that may stay empty. Mail uses the existing explicit Base mail provider and sender. Webhooks require a separately installed, qualified `INTEGRATION_EGRESS.send(WireRequest)` RPC service, Base's egress service (`src/modules/egress-service`): no ordinary-fetch or loopback production fallback exists. The shipped deploy.json declares no egress binding; docs/ALERT-WEBHOOKS.md gives the `services` entry to add after installing it. Missing configuration is blocked visibly. Destinations remain scoped to projects, and uncertain sends never retry automatically. See [the signed payload, replay and privacy contract](docs/ALERT-WEBHOOKS.md).

The owner deploy declaration selects SendGrid, requires the destination and hash-key secrets, and installs the minute cron. Supply the existing mail API key and sender for that provider. The production build uses the same Worker-first asset routing as hosted deploy: public static assets go to `ASSETS`, while application documents and API handlers retain their own authorization. `pnpm test:alerts` includes native scheduled events against the emitted Worker and waits for a real complete minute of accepted spike events; local mail is suppressed and network-edge fixtures cover provider submission in tests. These local checks do not establish provider cron dispatch or recipient delivery.

Archives disable alert rules and quarantine historical delivery metadata. Email destinations, active intents, spike cursors, Base integration queues and mail jobs are excluded; restoring never resends old alerts. Reconnect destination configuration and enable a new rule revision for subsequent captures.

## First project and sign-in destination

The product installer opens the local owner without email with returnTo `/setup`. Its successful exit means the owner was claimed and its temporary runtime stopped; a fresh installation still needs its first project. The ordinary sign-in form defaults to `/setup`, including sent/failed redisplay, and preserves explicit safe local deep links. Confirmation keeps the destination stored when the link was issued. Spool mounts its product project-creation gate, not the retained Base owner-confirmation form in `src/base/setup.ts`; that inherited form and its `/signin` path do not define the public product flow.

After native owner authentication, document requests from an installation without projects redirect to `/setup`. That page creates a project through the existing same-origin owner `POST /api/projects` transaction and retains the once-shown ingest key only in memory, with copy and explicit Continue actions. Failed requests leave a usable form; when a response is lost, reload to check durable state before retrying. Losing the shown key does not undo the project: generate a replacement in Settings.

At least one durable project completes first run. Existing and ordinarily restored projects count immediately, including after a runtime restart; an empty restore/reset does not count. Completed `/setup` requests return a finite 303 to `/`; `/app` retains its redirect to `/`. Re-running setup preserves project data and cannot manufacture completion. Health, auth, local outbox, static assets and authenticated APIs remain available independently of this document gate, including empty project listing, archive and restore. Before creating the first project, `/setup` offers **Restore a private archive**, which opens the existing archive form without creating a project. Successful restore refreshes the project inventory and lets the restored projects complete first run. The stopped-runtime CLI import procedure above is also available.

## Verification and owner acceptance

`rer verify` runs the actual build/typecheck, native Worker/D1 runtime/product/auth/restore checks and public CLI first-run journeys at 1440 and 390. It also runs real local alert outbox and HMAC receiver flows with rules/delivery UI, second-origin mapped browser capture and regression triage, unmodified Sentry browser/node 11.6.0 DSN-only programs, synthetic importer dry run/apply/guarded undo and grant scopes, and signed first/second owner claim with responsive editor, CSP and logout revocation. See [ACCEPTANCE.md](ACCEPTANCE.md) for the evidence required from those runs.

Record the exact final source head, CI result, final local verification, responsive evidence review, local operations check and clean-history publish-check in the owner's completion report. Source checks and generated inputs establish local structure only. The owner retains Sentry credentials and real exports, live accounts, mail sender configuration, provider/deploy calls, npm/public visibility changes, repository rename, tags, installation receipts and paid calls. No product document substitutes for those results.
