# Factory GPT Provider Runtime Repair — request

audience: AI coding agents first.

**Goal:** Make the shipped GPT Sol provider runtime actually work end to end: enrollment succeeds, health is honest, `factory-gpt` can reach Factory, legacy headerless consumers keep working, context accounting counts what is really sent, and every acceptance item is proven by a test.

**Context:** `31067387b` (in this worktree) added `modules/gptbridge/bin/solwebctl`, `modules/gptbridge/bin/factory-gpt`, session-aware `solwebd`/`translate`, a Pi extension, and deploy linking. Independent review found the happy path unreachable. Contracts: `docs/specs/2026-08-11-factory-gpt-model-design.md`, `docs/plans/2026-08-11-factory-gpt-provider-runtime.md`. Both remain authoritative — repair against them, never weaken them.

Out of scope, forbidden to modify: `modules/harness/factory/**`, `apps/web/**`, `collector/**`.

## Defects to repair

Each item names the observed defect. Fix the cause; do not paper over it.

1. `solwebd.py` health returns `account` from an attribute the engine never sets, so `solwebctl ensure` can never report `ok`, and `factory-gpt` always exits "not ready" after spawning a second daemon on top of a live one. Health MUST report the real authenticated account and MUST NOT spawn a duplicate daemon when one is already running.
2. `solwebctl enroll --current-profile` probes `chat.authenticated_account()` without first navigating to `https://chatgpt.com/` and waiting ready, so the relative `/api/auth/session` fetch always fails and `identity.json` is never written. Enrollment MUST succeed for a logged-in profile.
3. Health hardcodes `state: ready`, `authenticated: true`, `active: 0`, and never consults the pool's cap state, so `capped`/`logged_out`/`degraded` are unreachable and `cap_resume_at` never appears. Every field MUST come from real runtime state; unknown MUST render as unknown, never as a fabricated value. `version` MUST be installed build/commit identity, not the literal `local`.
4. `solwebctl stop` POSTs `/shutdown` and returns immediately, dropping the profile-lock drain `sw.sh` previously had. `stop` MUST wait for process exit (bounded) and MUST fail non-zero when it cannot confirm it.
5. `sw.sh` now hard-requires enrollment and takes the token from `solwebctl token` without checking failure, breaking legacy headerless CCR/`sw.sh`/ask-gpt callers and reporting "daemon down" for "not enrolled". Legacy headerless behavior MUST be preserved and failures MUST be distinguishable.
6. `translate.estimate_input` is always called without `tools`, and neither the rendered protocol preamble nor correction prompts are counted, so the estimate misses the dominant term (~145KB observed) and the ceiling never fires. Estimation MUST match the spec's estimator contract exactly.
7. Genuine context overflow raises the same `ReplayNeeded` used for benign no-delta, so overflow silently discards the conversation; when replay overflows the client sees HTTP 400 with a private kind. Overflow MUST be its own signal and MUST reach the client as OpenAI's `context_length_exceeded`.
8. Once keepalives start (the normal path for 10-20s turns) typed failures lose HTTP status and `Retry-After`. Typed failure metadata MUST survive the streaming path.
9. `modules/workstation/pi/agent/models.json` is unmodified and nothing installs `modules/workstation/pi/agent/extensions/`, so provider `gpt` resolves nowhere and `pi --list-models` finds no `gpt/sol-web-*`. Register the provider and install whatever the runtime actually loads; keep the extension only if it is required and proven, and match the spec's model names.
10. `factory-gpt` leaves `$CTL` unquoted and never verifies that `factory` and the preset `gpt-sol-<effort>` exist before exec. Preflight MUST fail before any Factory session is created.
11. `solwebd` answers 400 for an invalid session header before draining the request body on a keep-alive connection, desynchronising the next request on that socket.
12. `install.sh --links-only` ignores `--dry-run` and mutates `~/.local/bin`.

## Acceptance

Every item below is proven by a new or extended test under `modules/gptbridge/tests/` using the existing fake browser/scripted endpoint. No live Sol model call. Raw model probes are forbidden.

- `solwebctl enroll/ensure/health/token/stop` — identity pinning, ownership checks, honest states including `capped` with `cap_resume_at`, bounded stop, no duplicate daemon.
- `factory-gpt` — default effort `pro`, effort→preset mapping, missing preset/absent factory/unready provider all fail before Factory, exit code and signal preservation.
- Legacy headerless `sw.sh`/CCR/ask-gpt path and `sol-web-instant` still work.
- Session header validation, `session_key` isolation for byte-identical concurrent prompts, and retry-stable identity.
- Typed failures (`429`/`503`/`502`/timeout/cancel) preserved through blocking AND keepalive-streaming responses.
- Context estimate includes preamble, tool schemas and correction prompts; overflow surfaces as `context_length_exceeded`.
- Provider discovery: all four `gpt/sol-web-*` IDs resolve through installed Pi in a default and an account-composed `PI_CODING_AGENT_DIR`.
- `OVERDECK_DEPLOY_DRY_RUN=1 bash packaging/deploy-local.sh` performs no mutation; `install.sh --dry-run --links-only` performs no mutation.

Run clean:

```text
python3 -m pytest modules/gptbridge/tests/ -q
```

Address every warning and notice; never suppress one to go green.
