# Hetzner Ardent Runtime Contract

## Canonical Locations

| Purpose | Location |
| --- | --- |
| SSH alias | `dev-lane-coder-1` |
| Canonical remote slot | `/home/codex/workspace/dittto-fresh-1/web` |
| Other independent slots | `dittto-fresh`, `dittto-fresh-2`, `dittto-fresh-3` |
| Runtime state | `/home/codex/.local/state/sellable-ardent-development` |
| Runtime env | `.env.remote-dev.local` or `.env.remote-uat.local` |
| Ardent branch | Deterministically derived from the active Git branch |
| Local Supabase API | `127.0.0.1:54321` |
| Development app | `127.0.0.1:3111` |
| Production-build app | `127.0.0.1:3121` |
| Guarded gateway | `127.0.0.1:3112` |

## Development Mode

The committed launcher creates or resumes the active Git branch's Ardent copy
before serving:

```bash
cd /home/codex/workspace/dittto-fresh-1/web
infra/dev-lane/ardent-branch-workspace.sh name
tmux new-session -d -s sellable-dev 'npm run dev:ardent'
```

Development mode recompiles routes on demand. It is best for editing but its
first request to a page or API can be much slower than subsequent requests.

## Production-Build Mode

Build in the intended standalone clone so another slot's `.next` directory is
never overwritten. The committed command applies the 12 GB heap and required
safe provider placeholder (`NODE_OPTIONS=--max-old-space-size=12288` and
`GROQ_API_KEY=disabled-local-only`):

```bash
cd /home/codex/workspace/dittto-fresh-2/web
npm run build:ardent
```

Start the built app directly; do not use `npm start` unless the test-database
preflight in that script is intentionally required. `npm run start:ardent`
executes `next start -H 127.0.0.1 -p 3121` for slot 1:

```bash
tmux new-session -d -s sellable-prod \
  'cd /home/codex/workspace/dittto-fresh-2/web &&
   npm run start:ardent'
```

Build-time `NEXT_PUBLIC_*` values are embedded in browser bundles. Rebuild
after changing the public app or Supabase gateway URL.

Production-build mode removes on-demand compilation and hot refresh. Expect
faster and more consistent steady-state requests, but database/network latency
is unchanged and each code edit requires another build.

The application build can exceed Node's default heap even when the host has
plenty of free RAM. Keep the explicit 12 GB heap setting above; diagnose host
memory separately with `free -h` instead of treating a V8 4 GB heap failure as
a machine-capacity failure.

Some route modules instantiate provider SDKs while Next.js collects page data.
Use `disabled-local-only` placeholders for required provider keys such as
`GROQ_API_KEY`; never solve a development build failure by copying the real
production provider secret.

## Git Branch Lifecycle Hook

`infra/dev-lane/ardent-branch-workspace.sh` is the lifecycle hook. It derives a
stable name from the active Git branch, creates or resumes that Ardent branch,
links local Supabase, and generates the mode-0600 runtime environment before
starting the app. This is deliberately not an Ardent `branch_sql` hook.

Use:

```bash
npm run ardent:setup
npm run ardent:status
npm run dev:ardent
npm run preview:ardent
npm run ardent:down
```

Each standalone clone derives its own default dev/prod ports:
`dittto-fresh` 3011/3021, `dittto-fresh-1` 3111/3121,
`dittto-fresh-2` 3211/3221, and `dittto-fresh-3` 3311/3321.

The live failure and repair are part of the contract: a prior data-mutating
`branch_sql` hook rewrote copied data, made the branch unlike production, and
broke application paths. The working branch has no data-mutation hook. Safety
comes from Ardent/local credentials, generated host-local secrets, disabled
provider placeholders, disabled dispatchers/live modes, and independent
production-credential verification.

## Performance Diagnosis

Before comparing development and production-build modes, prove there is one
runtime owner per port. Repeated failed starts can leave childless `dotenvx`
processes with parent PID 1 consuming CPU even though they own no listener.
Identify the active tmux process tree, listeners, and each candidate's parent
and children. Stop only a candidate that is all of the following:

- not in the active tmux process tree;
- parented by PID 1;
- has no children;
- matches the expected remote development command; and
- owns no listener.

Measure separately:

1. browser to guarded gateway;
2. gateway to loopback Next.js;
3. a warm `SELECT 1` through the configured Prisma URL;
4. repeated hot API requests; and
5. local Supabase health.

`next start` removes compilation and hot-refresh work. It does not remove
Ardent network latency or improve an expensive database query. If a warm
`SELECT 1` is materially slower than local Supabase health and hot API routes
multiply that cost, investigate branch placement and route query count/indexes
instead of blaming ngrok.

## Guarded ngrok Access

Copy this skill's `scripts/uat-gateway.mjs` to the private runtime-state
directory. Store a random gate token in `uat-gate-token` with mode `0600`.
Start the gateway with the selected app port and local Supabase port:

```bash
state=/home/codex/.local/state/sellable-ardent-development
tmux new-session -d -s sellable-uat-gateway \
  "cd /home/codex/workspace/dittto-fresh-1/web &&
   npx dotenvx run -f .env.remote-uat.local -- bash -lc '
     UAT_GATE_TOKEN=\"\$(<${state}/uat-gate-token)\" \
     UAT_APP_PORT=3111 \
     UAT_SUPABASE_PORT=54321 \
     node ${state}/uat-gateway.mjs
   '"
```

Set `UAT_APP_PORT=3121` for production-build mode. Start ngrok against the
gateway, never the app or Supabase port:

```bash
tmux new-session -d -s sellable-ngrok \
  '/home/codex/.local/bin/ngrok http 3112 \
   --log=stdout --log-format=json'
```

Read the temporary URL from `http://127.0.0.1:4040/api/tunnels`. Append
`uat_access=<gate-token>` once to set the secure gateway cookie, then use
`/api/dev/auto-login?redirect=/`. Never commit the temporary URL or token.

## Verifiers

On Hetzner:

```bash
npx dotenvx run -f .env.remote-uat.local -- \
  node scripts/dev-lane/verify-ardent-remote-development.mjs --require-app
```

On the trusted laptop:

```bash
node scripts/dev-lane/verify-remote-production-credential-isolation.mjs \
  --ssh-alias dev-lane-coder-1 \
  --source-env /Users/christianreyes/dev/dittto-fresh/web/.env \
  --require-runtime-env
```

The expected receipt includes `productionShapedData: true`,
`branchDataMutatedByHook: false`, `productionCredentialsInstalled: false`, and
`productionWritesAllowed: false`.
