# Peer-link migration startup reproduction

AI-assisted; verified with the tests and tools listed above.

These are real server startup observations, not test results. PNGs render captured
server-output excerpts and read-only database queries; they are not app screenshots.
All database contents are synthetic. No live database was read or copied.

Base: f7b81e1d1115bc7284b39161d3cd6c7f3184eb1c
Head: 79b586f276a23c07b8aecbed2fab677b9f9ed28b

## Fixture

Install each checkout with `vp i`. Put the accompanying `proof-seed.ts` temporarily
at `apps/server/proof-seed.ts` in head. With no server using the destination,
run from head:

```sh
mkdir -p "$EVIDENCE/fixture"
vp exec node apps/server/proof-seed.ts "$EVIDENCE/fixture/statev2.sqlite"
```

The seed runs the application's actual migration service through 072 on a fresh
SQLite file, inserts one synthetic peer-link row, and deliberately removes ledger
entries 070–072. This creates the reported mismatch without inventing an ordinary
UI path to it. The resulting ledger ends at 069, but the schema already has the
peer-link table, fenced column and waiting-link index. Later migrations have not
run on this fixture.

Copy this closed file to four independent base directories, each as
`userdata/statev2.sqlite`: `before`, `after`, `before72`, `after72`.
For the last two only, record 070 and 071 to expose 072 independently:

```sql
INSERT INTO effect_sql_migrations(migration_id,name)
VALUES(70,'PeerLinks'),(71,'PeerLinkFence');
```

## Real server runs

Run the following command in each checkout, with the corresponding data directory
and a free port. Keep each successful process's PID for shutdown.

```sh
T3CODE_DEV_SEED=0 vp exec node apps/server/src/bin.ts \
  --base-dir "$EVIDENCE/$SIDE" --host 127.0.0.1 --port "$PORT" --no-browser
```

Observed runs:

| Checkout | Data directory | Port | Result |
|---|---|---|---|
| base | before | 18433 | exits 1, duplicate fenced column at 071 |
| head | after | 18432 | records 070–080, Listening on |
| base | before72 | 18434 | exits 1, existing waiting-link index at 072 |
| head | after72 | 18435 | records 072–080, Listening on |

Read-only SQLite checks on all four databases:

```sql
SELECT max(migration_id) FROM effect_sql_migrations;
SELECT * FROM peer_environment_links ORDER BY environment_id;
PRAGMA integrity_check;
```

The peer-link row was identical to the seed in all four databases, including its
`fenced=1` value. Every integrity check returned `ok`. Ledger maxima were 69,
80, 71 and 80 respectively. The accompanying capture.py renders these checks and
real log excerpts into before.png and after.png. It expects each raw startup log
at `<side>.raw.log` beside itself and each database under `<side>/userdata`.

## Additional client observation

For each main case, run that checkout's real web client from `apps/web` with
`T3CODE_DEV_SEED=0 T3CODE_PORT=<backend-port> PORT=<web-port> vp run dev --host 127.0.0.1`.
Chromium viewport was 1280 by 800, default light theme on both sides.
The base web client showed `Something went wrong` and HTTP 502 during
fetch-session-state because its backend had exited. The head client successfully
paired with its isolated server and loaded the chat view. Pairing credentials and
provider metadata are excluded from public evidence.

A real CLI project-add request to the head server also succeeded:

```sh
T3CODE_DEV_SEED=0 vp exec node apps/server/src/bin.ts project add \
  "$EVIDENCE/fixture" --title 'Migration recovery proof' \
  --base-dir "$EVIDENCE/after"
```

No agent turn, paid model, unit test, or test server was used as evidence.
The original cause of the ledger/schema mismatch through normal app actions is
unknown; this proof starts from the exact reported synthetic mismatch.
