diff --git a/.planning/MILESTONES.md b/.planning/MILESTONES.md
index 683cdd0..a5f414d 100644
--- a/.planning/MILESTONES.md
+++ b/.planning/MILESTONES.md
@@ -1,5 +1,27 @@
# Milestones
+## v4.0 Win-the-Drop (Shipped: 2026-06-25)
+
+**Phases completed:** 7 phases, 29 plans
+
+**Delivered:** ShopPyBot now completes *verified* orders on limited-release drops and survives multi-hour unattended runs without one fault taking down the rest.
+
+**Key accomplishments:**
+
+- **Safety gate (P18):** Central monitor-only mode + concrete `place_order_guarded()` on the `RetailerPlugin` ABC routes all 7 bundled plugins' place-order clicks through one guard, closing the confirmed 6-of-7 `test_mode` hole (CI grep + zero-write integration test). `CheckoutConfig` schema foundation for downstream phases.
+- **Verified checkout (P19):** URL-first order-confirmation detector (`core/confirmation.py`) plus idempotent `order_id`/`confirmed_at`/`checkout_attempts` columns — `purchased` is written only on a real order number, never a button click.
+- **Checkout profile + form-fill (P20):** 9-key CredentialStore shipping/billing profile, BestBuy + Amazon form-fill, CVV via `getpass` at runtime only, AST CI assertion proving no CVV leaks to logs; no full card data persisted.
+- **Bounded retry (P21):** Single `RetryPolicy` in `core/retry.py` shared by supervisor restart and cart-retry; per-step `asyncio.timeout()` per DOM stage; DB idempotency re-read before each attempt (no double-buy).
+- **Supervisor + relaunch (P22):** Per-coroutine supervision with failure budget absorbs crashes before the TaskGroup boundary; full browser relaunch (teardown→stealth→proxy→login); `sqlite3.OperationalError` read isolation; per-item timeout; cross-platform SIGTERM/SIGINT teardown bridge.
+- **Encrypted sessions (P23):** Fernet-encrypted per-platform cookie persistence (`core/session_store.py`) via raw CDP restore that bypasses the nodriver `set_all()` bug; skips re-login/MFA across restarts, no plaintext on disk.
+- **Health surface + server safety (P24):** `HealthRegistry`, expanded `BotService.get_status()` (per-plugin liveness/heartbeat), `health_degraded` fan-out alert, `shoppybot status` CLI, headless pygame import-crash guard.
+
+**Audit:** `.planning/milestones/v4.0-MILESTONE-AUDIT.md` — 17/17 requirements, 7/7 phases verified, 6/6 integration seams clean, suite 755 passed / 2 skipped. Status `tech_debt` (no blockers).
+
+**Known deferred items at close: 17** (see STATE.md → Deferred Items) — 7 live-environment UAT scenarios + 7 `human_needed` verifications (deferred per autonomous live-UAT policy), 1 todo (Amazon WAF auto-solve), 2 dormant seeds (SEED-001 repo scrub, SEED-002 release-please) — all explicitly Out of Scope for v4.0.
+
+---
+
## v3.0 v3.0 (Shipped: 2026-06-10)
**Phases completed:** 6 phases, 21 plans, 32 tasks
diff --git a/.planning/PROJECT.md b/.planning/PROJECT.md
index 2168030..09154d3 100644
--- a/.planning/PROJECT.md
+++ b/.planning/PROJECT.md
@@ -16,26 +16,38 @@ Target user: technically capable individuals who want automated stock monitoring
## Current State
-**Shipped v3.0 Resilience + Ecosystem (2026-06-10).** Built on the v2.0 modular core (`BotService` API behind a CLI-default front-end plus an optional FastAPI web UI; runtime-selected `CredentialStore` with no plaintext on disk). v3.0 added anti-detection (JS stealth + proxy rotation with ban detection; opt-in 2captcha reCAPTCHA-v2 solving with balance check and spend cap), the plugin ecosystem (ABC `difficulty`/`requires_proxy`/`requires_captcha` attrs, `shoppybot plugins list`, a GitHub-wiki registry spec, contributor docs), and price monitoring (per-item target price, price history table, price-drop fan-out alerts, `items price-history` CLI).
+**Shipped v4.0 Win-the-Drop — Acquisition Core + Reliability (2026-06-25).** Built on the v3.0 resilience/ecosystem layer and the v2.0 modular core (`BotService` API behind a CLI-default front-end plus an optional FastAPI web UI; runtime-selected `CredentialStore` with no plaintext on disk). v4.0 makes the bot complete *verified* orders on limited-release drops and survive multi-hour unattended runs: a central monitor-only gate + `place_order_guarded()` ABC closing the 6-of-7 `test_mode` hole; order-confirmation detection (`purchased` only on a real order number); checkout profile + BestBuy/Amazon form-fill with CVV-at-runtime; one unified `RetryPolicy` with per-step timeouts and idempotent cart-retry; per-coroutine supervisor with browser relaunch, DB read isolation, per-item timeout, and a SIGTERM/SIGINT teardown bridge; Fernet-encrypted session persistence; and a per-plugin health surface (`get_status`, `health_degraded` alert, `shoppybot status`) plus a headless pygame import-crash guard.
-v3.0: 6 phases (12-17) / 21 plans, all complete. Full suite: 548 passed, 2 skipped.
+v4.0: 7 phases (18-24) / 29 plans, all complete. Full suite: 755 passed, 2 skipped. Audit status `tech_debt` (no blockers; pre-accepted live-UAT debt).
-**Deferred (carried):** v2.0 live cross-OS/UI manual checks (keyring restart persistence, masked-TTY setup, web dashboard live render, 0.0.0.0 warning) tracked in STATE.md → Deferred Items; Amazon WAF CAPTCHA auto-solve deferred (degrades to manual pause).
+**Deferred (carried):** all live-environment UAT (monitor-only/confirmation/form-fill/relaunch/SIGTERM/session/headless) tracked in STATE.md → Deferred Items as the operator's pre-production live-buy checklist; Amazon WAF CAPTCHA auto-solve (manual-pause fallback); public-release hardening — git-history scrub/squash (SEED-001) + release-please tagging (SEED-002).
-**Key constraints (held):** secrets never in config.yml/logs/SQLite plaintext; GUI optional, CLI default; the credential-managing web UI binds to localhost by default.
+**Key constraints (held):** secrets never in config.yml/logs/SQLite plaintext; full card number / CVV never persisted to disk or logs (retailer-saved payment + CVV-at-runtime only); GUI optional, CLI default; the credential-managing web UI binds to localhost by default.
-## Current Milestone: v4.0 Win-the-Drop (Acquisition Core + Reliability)
+## Next Milestone
+
+**No active milestone.** v4.0 Win-the-Drop shipped 2026-06-25. Start the next cycle with `/gsd:new-milestone` (questioning → research → requirements → roadmap). Phase numbering continues from 24.
+
+Candidate directions from the v4.0 deferral list:
+- **Public-release hardening** — git-history scrub/squash (SEED-001) + release-please version tagging (SEED-002); a dedicated release milestone.
+- **Checkout form-fill for the remaining 5 retailers** (v4.0 covers BestBuy + Amazon).
+- **Request/API-mode (hybrid) checkout** — faster than DOM but per-site reverse-engineering and an arms race.
+- **Outcome analytics** (success rate, time-to-checkout) built on the BUY-04 order records.
+- **Richer health/observability on the web dashboard** (beyond the CLI/status payload).
+
+
+Shipped: v4.0 Win-the-Drop (Acquisition Core + Reliability) — 2026-06-25
**Goal:** Make ShopPyBot complete *verified* orders on limited-release drops, and survive multi-hour unattended runs without one fault taking everything down.
-**Target features:**
-- Acquisition Core: checkout profile (shipping/billing) + form-fill on 1-2 reliable retailers (BestBuy, Amazon); order-confirmation capture (mark `purchased` only on a real confirmation, not a button click); bounded retry-on-cart with backoff; per-item/per-step checkout time budget; a central monitor-only run mode that also closes the `test_mode` place-order hole (6 of 7 plugins).
-- Always-On Reliability: per-coroutine supervision + backoff restart; browser-crash detection + relaunch; encrypted session/cookie persistence; DB read-path error isolation; per-item orchestrator timeout; structured health/heartbeat surface; one unified transient retry/backoff.
+**Delivered features:**
+- Acquisition Core: checkout profile (shipping/billing) + form-fill on BestBuy + Amazon; order-confirmation capture (mark `purchased` only on a real confirmation, not a button click); bounded retry-on-cart with backoff; per-item/per-step checkout time budget; a central monitor-only run mode that also closes the `test_mode` place-order hole (6 of 7 plugins).
+- Always-On Reliability: per-coroutine supervision + backoff restart; browser-crash detection + relaunch; encrypted session/cookie persistence; DB read-path error isolation; per-item orchestrator timeout; structured health/heartbeat surface; one unified transient retry/backoff (`RetryPolicy`).
- Opportunistic server-safety: headless pygame import-crash guard; SIGTERM/SIGINT teardown bridge (stop orphaning Chrome).
-**Deferred to follow-on:** request/API-mode checkout, virtual-waiting-room/queue survival (Queue-it/PerimeterX/Akamai/DataDome), multi-account/multi-profile parallel attempts, Amazon WAF auto-solve. All XL arms-race or ToS-hostile.
+**Constraints held:** v2.0 security posture (no plaintext secrets, CLI default, web optional/localhost); payment via retailer-saved methods + CVV-at-runtime (never persist full card data, PCI); checkout work targets the `nodriver` plugin stack.
-**Key constraints:** preserve the v2.0 security posture (no plaintext secrets, CLI default, web optional/localhost); payment via retailer-saved methods + CVV-at-runtime (never persist full card data, PCI); checkout work targets the `nodriver` plugin stack; continues phase numbering from 17.
+
## Requirements
@@ -75,19 +87,25 @@ v3.0: 6 phases (12-17) / 21 plans, all complete. Full suite: 548 passed, 2 skipp
- ✓ Price monitoring: per-item target price, price history table, price-drop fan-out alerts, price-history CLI — v3.0 (Phase 16)
- ✓ Stability: v2.0 audit tech-debt resolved + test hardening (548 tests) — v3.0 (Phases 12, 17)
-### Active (v4.0 Win-the-Drop — Acquisition Core + Reliability)
+### Validated (shipped v4.0 Win-the-Drop — Acquisition Core + Reliability)
+
+- ✓ Acquisition: checkout profile (shipping/billing) + form-fill (BestBuy, Amazon) — v4.0 (Phase 20)
+- ✓ Acquisition: order-confirmation capture / verified purchase — v4.0 (Phase 19)
+- ✓ Acquisition: bounded retry-on-cart with backoff (idempotent, no double-buy) — v4.0 (Phase 21)
+- ✓ Acquisition: per-item/per-step checkout time budget — v4.0 (Phases 21, 22)
+- ✓ Acquisition: central monitor-only run mode + close test_mode place-order hole — v4.0 (Phase 18)
+- ✓ Reliability: per-coroutine supervision + backoff restart — v4.0 (Phase 22)
+- ✓ Reliability: browser-crash detection + relaunch — v4.0 (Phase 22)
+- ✓ Reliability: encrypted session/cookie persistence — v4.0 (Phase 23)
+- ✓ Reliability: DB read-path error isolation — v4.0 (Phase 22)
+- ✓ Reliability: per-item orchestrator timeout — v4.0 (Phase 22)
+- ✓ Reliability: structured health/heartbeat surface — v4.0 (Phase 24)
+- ✓ Reliability: unified RetryPolicy (one backoff source) — v4.0 (Phase 21)
+- ✓ Server-safety: headless pygame import-crash guard + SIGTERM/SIGINT teardown bridge — v4.0 (Phases 24, 22)
+
+### Active (next milestone)
-- [ ] Acquisition: checkout profile (shipping/billing) + form-fill (BestBuy, Amazon)
-- [ ] Acquisition: order-confirmation capture / verified purchase
-- [ ] Acquisition: bounded retry-on-cart with backoff (idempotent, no double-buy)
-- [ ] Acquisition: per-item/per-step checkout time budget
-- [ ] Acquisition: central monitor-only run mode + close test_mode place-order hole
-- [ ] Reliability: per-coroutine supervision + backoff restart
-- [ ] Reliability: browser-crash detection + relaunch
-- [ ] Reliability: encrypted session/cookie persistence
-- [ ] Reliability: DB read-path error isolation
-- [ ] Reliability: per-item orchestrator timeout
-- [ ] Reliability: structured health/heartbeat surface
+_None yet — run `/gsd:new-milestone` to scope the next cycle. See "Next Milestone" above for candidate directions._
### Deferred
@@ -118,6 +136,11 @@ v3.0: 6 phases (12-17) / 21 plans, all complete. Full suite: 548 passed, 2 skipp
| (v2.0) Dynamic CredentialStore | OS keyring with encrypted-file fallback for headless Ubuntu, env-var last resort; secrets never plaintext on disk | Chosen |
| (v2.0) Local web UI via FastAPI | Most portable across Ubuntu/Windows, clean core/UI separation, optional extra; binds localhost by default | Chosen |
| (v2.0 reversal) No secrets in SQLite | Storing creds in the local DB is plaintext-on-disk, weaker than env/keyring; rejected in favor of CredentialStore | Chosen |
+| (v4.0) Single `place_order_guarded()` gate on the ABC | One enforcement point honors monitor-only/`test_mode` for all 7 plugins; closed the confirmed 6-of-7 hole instead of patching each plugin | ✓ Good |
+| (v4.0) `purchased` only on a confirmed order number | A button click is not a purchase; confirmation-URL + order-id capture is the idempotency anchor that prevents double-buy on retry | ✓ Good |
+| (v4.0) One unified `RetryPolicy` | Supervisor-restart and cart-retry share one backoff module so the two retry concepts cannot diverge or compound into a runaway loop | ✓ Good |
+| (v4.0) Retailer-saved payment + CVV-at-runtime | Never persist full card/PAN (PCI scope); CVV via `getpass`, never logged; AST CI assertion guards against leaks | ✓ Good |
+| (v4.0) Live-environment UAT deferred as tracked debt | Live retail checkout is ToS/legal risk in CI; confirmation/form-fill selectors verified by manual UAT, tracked in STATE.md Deferred Items | — Pending (operator live-buy checklist) |
## Evolution
@@ -137,4 +160,4 @@ This document evolves at phase transitions and milestone boundaries.
4. Update Context with current state
---
-*Last updated: 2026-06-10 — v4.0 Win-the-Drop milestone started*
+*Last updated: 2026-06-25 — after v4.0 Win-the-Drop milestone (shipped)*
diff --git a/.planning/RETROSPECTIVE.md b/.planning/RETROSPECTIVE.md
new file mode 100644
index 0000000..25792de
--- /dev/null
+++ b/.planning/RETROSPECTIVE.md
@@ -0,0 +1,60 @@
+# Project Retrospective
+
+*A living document updated after each milestone. Lessons feed forward into future planning.*
+
+## Milestone: v4.0 — Win-the-Drop
+
+**Shipped:** 2026-06-25
+**Phases:** 7 (18-24) | **Plans:** 29 | **Suite:** 755 passed, 2 skipped
+
+### What Was Built
+- Safety gate: monitor-only mode + `place_order_guarded()` ABC closing the 6-of-7 `test_mode` place-order hole (P18).
+- Verified checkout: order-confirmation detection + `order_id`/`confirmed_at`/`checkout_attempts` columns — `purchased` only on a real order number (P19).
+- Checkout profile + BestBuy/Amazon form-fill, CVV-at-runtime, no card data persisted (P20).
+- Unified `RetryPolicy` + per-step `asyncio.timeout()` + idempotent cart-retry (P21).
+- Per-coroutine supervisor + browser relaunch + DB read isolation + per-item timeout + SIGTERM/SIGINT bridge (P22).
+- Fernet-encrypted session persistence via raw CDP restore, bypassing the nodriver `set_all()` bug (P23).
+- Health surface (`HealthRegistry`, `get_status`, `health_degraded` alert, `shoppybot status`) + headless pygame crash guard (P24).
+
+### What Worked
+- **Foundation phase first.** P18 (safety gate + `CheckoutConfig`) had "do first, all downstream depend on it" — every later phase could be built and tested with live orders suppressed by monitor-only. No phase risked a real purchase during development.
+- **Single enforcement points.** One `place_order_guarded()` on the ABC (not per-plugin patches) and one `RetryPolicy` (not per-call-site loops) — both backed by CI guards (grep/AST) that fail the build on regression.
+- **Idempotency designed before its consumer.** P19 added the `order_id` anchor; P21's cart-retry read it. Designing the anchor a phase ahead of the retry that needs it avoided a double-buy redesign.
+- **Clean integration close.** Integration checker verified all 6 cross-phase seams with 0 blockers; PLUGIN_API_VERSION stayed 2 (all additions additive).
+
+### What Was Inefficient
+- **Live UAT can't run on the dev box.** 17 live-environment items (confirmation/form-fill/relaunch/SIGTERM/session/headless selectors + scenarios) accumulated as deferred debt. Correct per policy, but the real acceptance bar for an acquisition bot is a live drop, which CI/dev can't exercise.
+- **STATE.md drifted stale.** It froze mid-Phase-23 (status "verifying", phase table showing 18-21 "Not started") while ROADMAP.md stayed authoritative. At milestone close this forced a desync diagnosis before any action was safe.
+- **ROADMAP Phase Details not pruned at v3.0 close.** Archived v3.0 phases 12-17 left full detail in the live ROADMAP, so `roadmap.analyze` false-positived them as incomplete `no_directory` phases — which would have driven an autonomous run to "re-execute" already-shipped work.
+
+### Patterns Established
+- **Foundation-phase-first** for any milestone that touches a dangerous action (place-order): ship the gate before the feature.
+- **Confirmed-outcome idempotency**: never treat a UI action as success; capture a retailer-side identifier and gate retries on it.
+- **Security invariants as CI guards**: AST assertion (no CVV in `writeLog` args), grep assertion (no `for attempt in range(` outside `core/retry.py`), zero-write integration test under monitor-only.
+- **Explicit UAT-debt ledger** in STATE.md Deferred Items — live checks that can't run in CI are tracked, not silently dropped.
+
+### Key Lessons
+1. **Prune ROADMAP "Phase Details" at every milestone close.** Leaving archived-milestone detail in the live ROADMAP desyncs `roadmap.analyze` and can mislead a future autonomous run. (Fixed this close: live ROADMAP now collapses to per-milestone `` only.)
+2. **Keep STATE.md in sync, or treat ROADMAP as the sole source of truth.** A stale STATE.md cost a diagnosis step at close. Prefer reconciling STATE at phase transitions.
+3. **Design idempotency anchors one phase ahead of the retry that reads them** — it removes rework and closes the double-buy window by construction.
+
+### Cost Observations
+- Model mix: not tracked this milestone.
+- Notable: milestone closed via `/gsd-autonomous` lifecycle tail after the phase work + audit were already complete; the run's main value was catching the ROADMAP/STATE desync before executing anything.
+
+---
+
+## Cross-Milestone Trends
+
+### Cumulative Quality
+
+| Milestone | Tests (suite) | Notable |
+|-----------|---------------|---------|
+| v2.0 | 341 passed | Modular core + CredentialStore (no plaintext on disk) |
+| v3.0 | 548 passed, 2 skipped | Anti-detection + ecosystem + price monitoring |
+| v4.0 | 755 passed, 2 skipped | Verified checkout + always-on reliability |
+
+### Top Lessons (Verified Across Milestones)
+
+1. Prune archived-milestone detail from the live ROADMAP at close — keeps `roadmap.analyze` accurate and ROADMAP constant-size.
+2. Single enforcement points (one ABC gate, one RetryPolicy) backed by CI guards beat per-site patches for safety-critical invariants.
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index bca98da..027ead1 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -11,7 +11,7 @@
- ✅ **v1 Open Source Launch** — Phases 1-6 (shipped 2026-06-03)
- ✅ **v2.0 Modular Core + Cross-Platform UX** — Phases 7-11 (shipped 2026-06-06)
- ✅ **v3.0 Resilience + Ecosystem** — Phases 12-17 (shipped 2026-06-10)
-- **v4.0 Win-the-Drop (Acquisition Core + Reliability)** — Phases 18-24 (in progress)
+- ✅ **v4.0 Win-the-Drop (Acquisition Core + Reliability)** — Phases 18-24 (shipped 2026-06-25)
---
@@ -27,6 +27,8 @@
- [x] Phase 5: Notification System (5/5 plans) — 2026-06-03
- [x] Phase 6: Platform Expansion (5/5 plans) — 2026-06-03
+Full phase detail archived at `.planning/milestones/v1-phases` (see also `milestones/`).
+
@@ -46,382 +48,47 @@ Audit: `.planning/milestones/v2.0-MILESTONE-AUDIT.md` (status: passed).
✅ v3.0 Resilience + Ecosystem (Phases 12-17) — SHIPPED 2026-06-10
-- [x] **Phase 12: Stability Foundation** — Close v2.0 deferred cross-OS checks and resolve 4 audit tech-debt items (completed 2026-06-09)
-- [x] **Phase 13: Anti-Detection Layer 1 — Fingerprint + Proxy** — Apply JS fingerprint stealth patch and implement proxy rotation with ban detection (completed 2026-06-09)
-- [x] **Phase 14: Anti-Detection Layer 2 — CAPTCHA Solving** — Integrate 2captcha opt-in solver with CredentialStore key, startup balance check, async executor wrapping, and spend cap (completed 2026-06-09)
-- [x] **Phase 15: Plugin Ecosystem Registry** — Add difficulty/proxy/captcha class attrs to ABC, create GitHub wiki registry table, ship `shoppybot plugins list` command (completed 2026-06-09)
-- [x] **Phase 16: Price Monitoring** — Per-item target price, append-only price history table, percentage-drop trigger, fan-out price-drop alerts, price-history CLI (completed 2026-06-10)
-- [x] **Phase 17: Test Hardening** — Unit and integration coverage for all v3.0 features (completed 2026-06-10)
+- [x] Phase 12: Stability Foundation (4/4 plans) — 2026-06-09
+- [x] Phase 13: Anti-Detection Layer 1 — Fingerprint + Proxy (3/3 plans) — 2026-06-09
+- [x] Phase 14: Anti-Detection Layer 2 — CAPTCHA Solving (3/3 plans) — 2026-06-09
+- [x] Phase 15: Plugin Ecosystem Registry (3/3 plans) — 2026-06-09
+- [x] Phase 16: Price Monitoring (4/4 plans) — 2026-06-10
+- [x] Phase 17: Test Hardening (4/4 plans) — 2026-06-10
-Full phase detail archived in `.planning/milestones/v3.0-ROADMAP.md`.
+Full phase detail archived at `.planning/milestones/v3.0-ROADMAP.md`.
+Audit: `.planning/milestones/v3.0-MILESTONE-AUDIT.md`.
-### v4.0 Win-the-Drop (Phases 18-24)
-
-- [x] **Phase 18: Safety Gate + Config Foundation** — Central monitor-only mode, `place_order_guarded()` ABC method closing the 6-of-7 plugin safety hole, and `CheckoutConfig` schema as the foundation every downstream phase depends on (completed 2026-06-11)
-- [x] **Phase 19: DB Schema + Confirmation Detection** — Add `order_id`/`confirmed_at`/`checkout_attempts` columns and build `core/confirmation.py` so `purchased` is only written on a real confirmed order number, never on a button click (completed 2026-06-11)
-- [x] **Phase 20: Checkout Profile + Form-Fill** — Shipping/billing profile stored in CredentialStore (9 keys, no card data), BestBuy and Amazon form-fill, CVV getpass-only at runtime (completed 2026-06-11)
-- [x] **Phase 21: Per-Step Timeouts + Unified Retry + Cart-Retry** — One `RetryPolicy` in `core/retry.py` shared by both supervisor restart and cart-retry; per-step `asyncio.timeout()` per DOM stage; idempotency guard reads DB before every attempt (completed 2026-06-12)
-- [x] **Phase 22: Supervisor + Browser Relaunch + Server Safety** — Per-coroutine supervision with failure budget absorbs crashes before the TaskGroup boundary; full relaunch sequence (teardown, proxy, stealth, login); DB read isolation; per-item orchestrator timeout; SIGTERM/SIGINT teardown bridge (completed 2026-06-12)
-- [x] **Phase 23: Encrypted Session Persistence** — Fernet-encrypted cookie save/restore via `core/session_store.py` (reuses `EncryptedFileBackend` pattern); raw CDP restore path that bypasses the confirmed `set_all()` bug; replaces Phase 22's no-op stub (completed 2026-06-12)
-- [x] **Phase 24: Health Surface + Server Safety** — `core/health.py` HealthRegistry, expanded `BotService.get_status()` with per-plugin liveness/heartbeat, `health_degraded` notification event, updated FastAPI `/status` endpoint, headless pygame crash guard (completed 2026-06-12)
-
----
-
-## Phase Details
-
-### Phase 12: Stability Foundation
-
-**Goal**: The v2.0 deferred debt and audit tech-debt are paid down before new features land, so the test suite is a reliable baseline
-**Depends on**: Nothing — do first
-**Requirements**: STAB-01, STAB-02
-**Success Criteria** (what must be TRUE):
-
- 1. All 4 deferred v2.0 cross-OS/UI manual checks (keyring restart survival, masked-TTY passphrase prompt, web dashboard render on Ubuntu, `0.0.0.0` bind warning) are executed and documented pass or fail, with any failures fixed
- 2. Each of the 4 v2.0 audit tech-debt items has a targeted regression test that passes in CI
- 3. No broad refactors occur: only the specific items in scope are changed
-
-**Plans**: 4 plans
-
-Plans:
-
-- [x] 12-01-PLAN.md — TD-1: re-anchor logger logging_level read to core.paths.config_path() + regression test
-- [x] 12-02-PLAN.md — TD-2/TD-3: harden SC1 secret-read guard (rglob) and separator guard (__file__-anchored)
-- [x] 12-03-PLAN.md — TD-4 config write-seam regression test + accepted MOD-02 gap doc; MC-4 0.0.0.0 banner assertion
-- [x] 12-04-PLAN.md — Execute and document MC-1..MC-4 deferred manual checks in docs/PLATFORMS.md
-
-### Phase 13: Anti-Detection Layer 1 — Fingerprint + Proxy
-
-**Goal**: Users can enable proxy rotation and the bot applies a JS fingerprint stealth patch at browser startup, measurably reducing Layer 2 bot signals
-**Depends on**: Phase 12
-**Requirements**: ANTI-08, ANTI-04, ANTI-05
-**Success Criteria** (what must be TRUE):
-
- 1. Bot applies `window.chrome`, `navigator.plugins`, `navigator.languages`, and screen-dimension patches via `core/stealth.py` at every browser startup with no plugin ABC version bump
- 2. User can enable proxy rotation via an opt-in `proxy:` config section (disabled by default) listing `scheme://host:port` URLs; bot logs "Proxy rotation: enabled, pool_size=N" at startup
- 3. Bot detects ban signals (HTTP 403/429/503, challenge-redirect, block-phrase body) and rotates to the next proxy, retiring a proxy after N consecutive failures for a configurable cooldown period
- 4. WebRTC Chrome preferences are set at browser launch to prevent real-IP leaks through the proxy tunnel
- 5. Each proxy is scoped to its plugin instance (`self._proxy`) and rotated only at browser restart, not mid-session
-
-**Plans**: 3 plans
-
-Plans:
-
-- [x] 13-01-PLAN.md — core/stealth.py: STEALTH_JS + apply_stealth, ProxyPool (round-robin/retire/cooldown), proxy launch args + WebRTC flag, CDP Fetch auth, ban-signal detector (+ unit tests)
-- [x] 13-02-PLAN.md — ProxyConfig schema (opt-in, disabled by default) + documented sample.config.yml proxy section
-- [x] 13-03-PLAN.md — Wire stealth + proxy into BotService/orchestrator/registry and all 8 plugins; per-instance scoping, exact startup log, fail-loud on pool exhaustion, ban-detect recording
-
-**UI hint**: no
-
-### Phase 14: Anti-Detection Layer 2 — CAPTCHA Solving
-
-**Goal**: Users who encounter reCAPTCHA v2 or Amazon WAF CAPTCHAs can opt into automated solving via 2captcha with full cost visibility and no credential plaintext exposure
-**Depends on**: Phase 13 (fingerprint + proxy layer in place before adding CAPTCHA layer)
-**Requirements**: ANTI-06, ANTI-07
-**Success Criteria** (what must be TRUE):
-
- 1. User can enable CAPTCHA solving via `captcha.enabled: true` in config; the 2captcha API key is stored exclusively in CredentialStore (`TWOCAPTCHA_API_KEY`), never in config.yml
- 2. Bot checks 2captcha account balance at startup, logs a WARNING when balance is low, and skips solver use (falling back to manual pause) when balance is zero
- 3. CAPTCHA solve calls use `run_in_executor` + `asyncio.timeout(120)` so other plugin poll tasks are not blocked during a solve
- 4. A configurable `captcha.max_solves_per_run` limit prevents unbounded API charges; default config disables CAPTCHA solving
-
-**Plans**: 3 plans
-
-Plans:
-
-- [x] 14-01-PLAN.md — Foundation: TWOCAPTCHA_API_KEY in SECRET_KEYS, CaptchaConfig, CaptchaSolver 2captcha v1 client (submit/poll/balance/cap)
-- [x] 14-02-PLAN.md — Wiring: solver constructed fresh in async_main + startup balance check + registry.assign_solver (mirrors ProxyPool)
-- [x] 14-03-PLAN.md — Plugin solve path: Amazon + BestBuy reCAPTCHA solve under run_in_executor+timeout(120) with manual-pause fallback; WAF deferred
-
-### Phase 15: Plugin Ecosystem Registry
-
-**Goal**: Community contributors have a discoverable registry with clear difficulty ratings, and users can inspect loaded plugins locally without a network call
-**Depends on**: Phase 12
-**Requirements**: REG-01, REG-02, REG-03, REG-04
-**Success Criteria** (what must be TRUE):
-
- 1. Plugin authors can declare `difficulty`, `requires_proxy`, and `requires_captcha` as class attributes on any plugin; existing plugins without these attrs continue to load with sensible defaults (non-breaking)
- 2. The GitHub wiki registry table contains required fields for each community plugin: name, platform, domain patterns, maintainer, anti-detection difficulty, methods implemented, last-verified date, proxy-required, captcha-required
- 3. Running `shoppybot plugins list` displays all locally loaded plugins with their declared domain patterns, difficulty, and proxy/captcha flags without making a network call
- 4. CONTRIBUTING.md and the PR template require contributors to supply `difficulty`, `requires_proxy`, and `requires_captcha` for new plugin submissions
-
-**Plans**: 3 plans
-
-Plans:
-
-- [x] 15-01-PLAN.md — REG-02: add difficulty/requires_proxy/requires_captcha class attrs + __init_subclass__ difficulty validation to RetailerPlugin ABC (non-breaking, PLUGIN_API_VERSION stays 2) + test_plugin_base.py assertions
-- [x] 15-02-PLAN.md — REG-03: BotService.list_plugins() over registry._all_plugins + core/cli/plugins.py handler + plugins-list subparser with --json + no-network CLI tests
-- [x] 15-03-PLAN.md — REG-01/REG-04: docs/PLUGIN_REGISTRY.md 9-field wiki SPEC + CONTRIBUTING.md/PR-template/PLUGIN_DEV.md attr requirements + tests/test_docs.py
-
-### Phase 16: Price Monitoring
-
-**Goal**: Users can track per-item prices, receive fan-out alerts when prices drop to target or by a configured percentage, and inspect price history from the CLI
-**Depends on**: Phase 12
-**Requirements**: PRICE-01, PRICE-02, PRICE-03, PRICE-04, PRICE-05, PRICE-06
-**Success Criteria** (what must be TRUE):
-
- 1. User can set `target_price` (absolute) and `price_drop_pct` (percentage) per item in config; NULL/absent means price monitoring is off for that item
- 2. Bot records scraped prices in an append-only `price_history` SQLite table each poll cycle via an optional `get_price()` plugin ABC hook (default returns `None`); the DB migration is idempotent on existing installs
- 3. Price-drop alerts are dispatched through the existing fan-out notification dispatcher using a distinct `price_drop` notification_type with dedup columns separate from stock alert columns
- 4. Price alert payloads include the current price, target price, and percentage from target
- 5. Running `shoppybot items price-history ` displays the last N recorded prices for that item
-
-**Plans**: 4 plans
-
-Plans:
-
-- [x] 16-01-PLAN.md — Data layer: idempotent price_history table + 4 items columns + 8 parameterized price _sync functions + ItemConfig target_price/price_drop_pct (PRICE-01/02/05)
-- [x] 16-02-PLAN.md — Plugin + notification contract: NotificationEvent price fields, default-None get_price() ABC hook, real Amazon get_price() + text→cents parser, price_drop notifier branches (PRICE-02/04)
-- [x] 16-03-PLAN.md — Orchestrator wiring: _check_and_buy price path, both triggers with separate dedup, single price_drop dispatch, startup config seeding + BotService.get_price_history (PRICE-02/03/04/05)
-- [x] 16-04-PLAN.md — CLI: shoppybot items price-history leaf with --limit (default 10), $X.XX table, no network (PRICE-06)
-
-**UI hint**: yes
-
-### Phase 17: Test Hardening
-
-**Goal**: Every new v3.0 feature has unit and integration coverage so regressions are caught by CI before they reach users
-**Depends on**: Phases 13, 14, 15, 16 (tests validate the implemented features)
-**Requirements**: STAB-03
-**Success Criteria** (what must be TRUE):
-
- 1. Unit tests cover proxy config parsing, ban-signal detection logic, per-instance proxy scoping, and cooldown/retire logic
- 2. Unit tests cover CAPTCHA config parsing, balance-check behavior, executor wrapping, and spend-cap enforcement
- 3. Unit tests cover price comparison threshold logic, `price_history` DB schema (including idempotent migration against a v2.0 DB fixture), and price-drop dedup separation from stock-alert dedup
- 4. Integration tests cover the plugin ABC additions (`difficulty`, `requires_proxy`, `requires_captcha` defaults and overrides) and the `get_price()` hook being called alongside `check_availability`
-
-**Plans**: 4 plans
-
-Plans:
-
-- [x] 17-01-PLAN.md — Proxy coverage: PX-01..PX-06 (config validator, fetch-handler tasks, ProxyPool edges, registry routing/lifecycle isolation)
-- [x] 17-02-PLAN.md — CAPTCHA coverage: CP-01..CP-05 (poll/timeout errors, balance gate, solve_amazon_waf submit/poll/decode)
-- [x] 17-03-PLAN.md — Price + get_price integration: PR-01..PR-04 + AB-01, AB-02 (v2.0-schema migration fixture, trigger guards, get_price-alongside-check_availability)
-- [x] 17-04-PLAN.md — Plugin ABC: AB-03, AB-04 (metadata overrides + _handle_ban ban→proxy-cooldown bridge)
-
----
-
-### Phase 18: Safety Gate + Config Foundation
-
-**Goal**: Every plugin routes its final place-order action through an ABC-enforced gate that honors monitor-only mode, so no plugin — current or future — can place a live order when monitoring is active
-**Depends on**: Nothing (do first; all downstream v4.0 phases depend on this config foundation)
-**Requirements**: BUY-01, BUY-02
-**Success Criteria** (what must be TRUE):
-
- 1. User can start the bot with `--monitor-only` CLI flag or `debug.monitor_only: true` in config; bot performs stock checks and fires alerts but `auto_buy` is never called for any plugin
- 2. All 7 bundled plugins route their place-order DOM click through `place_order_guarded()` on the RetailerPlugin ABC; a CI test with all 7 plugins and `monitor_only=True` asserts zero calls to the purchase write-queue
- 3. BestBuy's confirmed `test_mode` gap is closed: `place_order.click()` is not called when `monitor_only=True` or `test_mode=True` on any plugin
- 4. `CheckoutConfig` sub-model exists in `AppConfig` with `item_timeout_secs`, `step_timeout_secs`, `max_cart_retries`, `backoff_base`, `backoff_jitter`, and `alert_on_errors` fields so downstream phases can use them without schema changes
-
-**Plans**: 4 plans
-
-Plans:
-
-**Wave 1**
-
-- [x] 18-01-PLAN.md — DebugConfig.monitor_only + CheckoutConfig model + AppConfig wiring (BUY-01, BUY-02)
-- [x] 18-02-PLAN.md — place_order_guarded() concrete method on RetailerPlugin ABC + unit tests (BUY-02)
-
-**Wave 2** *(blocked on Wave 1 completion)*
-
-- [x] 18-03-PLAN.md — orchestrator monitor_only gate + --monitor-only CLI flag + CVV short-circuit + ALLOWLIST (BUY-01)
-- [x] 18-04-PLAN.md — reroute all 7 plugins through place_order_guarded + test_safety_gate.py (7-plugin zero-write + grep) (BUY-02)
-
-### Phase 19: DB Schema + Confirmation Detection
-
-**Goal**: Purchases are only recorded when the bot has verified a real order number from the retailer's confirmation page, never on a button click alone
-**Depends on**: Phase 18 (monitor-only gate must be live before any live checkout UAT of confirmation selectors)
-**Requirements**: BUY-03, BUY-04
-**Success Criteria** (what must be TRUE):
-
- 1. The `items` table has `order_id TEXT`, `confirmed_at TEXT`, and `checkout_attempts INTEGER DEFAULT 0` columns added via idempotent `ALTER TABLE` (same pattern as v3.0 price columns); existing DB installs migrate without data loss
- 2. After `auto_buy()` returns, the orchestrator calls `detect_order_confirmation(tab, platform)` and only enqueues `("confirmed", link, order_id, ts)` to the write-queue when a non-None order_id is returned; `purchased=1` is set only at that point
- 3. When confirmation is not detected, the bot logs a WARNING and falls back to the legacy `purchased` write tag (no silent failure, no double-buy risk from the confirmation path itself)
- 4. Each confirmed checkout writes `order_id` and `confirmed_at` to the DB, providing the idempotency anchor for retry (Phase 21) to read before any re-attempt
-
-**Plans**: 4 plans
-
-Plans:
-
-**Wave 1** *(parallel-safe; no file overlap)*
-
-- [x] 19-01-PLAN.md — models.py: 3 idempotent confirmation columns (order_id/confirmed_at/checkout_attempts DEFAULT 0, NOT incremented) + update_item_confirmed_sync (BUY-04)
-- [x] 19-02-PLAN.md — core/confirmation.py NEW: detect_order_confirmation (URL-first/DOM-backup map, ~3s settle, Amazon orderID URL-param parse, CONFIRMED- sentinel) + FakeTab tests (BUY-03)
-- [x] 19-03-PLAN.md — core/plugin_base.py: additive sync get_active_tab() ABC default (returns main_tab; PLUGIN_API_VERSION stays 2) (BUY-03)
-
-**Wave 2** *(blocked on Wave 1)*
-
-- [x] 19-04-PLAN.md — orchestrator wiring (_try_auto_buy detect + confirmed/legacy fallback outside any timeout; _dispatch_write confirmed branch) + Amazon/BestBuy _last_tab + get_active_tab override + orchestrator tests (BUY-03, BUY-04)
-
-**Research flag** (RESOLVED via 19-RESEARCH.md): per-retailer confirmation URL patterns (Amazon `/gp/buy/thankyou`, BestBuy `/checkout/r/thank-you`) are HIGH confidence and hardcoded in `core/confirmation.py`; backup DOM selectors (`#confirmedOrderId`, `.thank-you-order-number`) are MEDIUM confidence — live UAT on a test_mode buy is tracked as UAT debt (STATE.md Deferred Items).
-
-### Phase 20: Checkout Profile + Form-Fill
-
-**Goal**: Users can configure a shipping/billing profile that the bot fills during BestBuy and Amazon checkout, with payment using the retailer-saved method plus CVV entered at runtime and no full card data persisted anywhere
-**Depends on**: Phase 18 (monitor-only gate required before any form-fill can be tested live); Phase 19 (confirmation detection should precede form-fill so a completed form-fill can be confirmed)
-**Requirements**: BUY-07
-**Success Criteria** (what must be TRUE):
-
- 1. User can run `shoppybot setup checkout-profile` to interactively populate 9 address keys (`CHECKOUT_FIRST_NAME`, `CHECKOUT_LAST_NAME`, `CHECKOUT_ADDRESS_LINE1`, `CHECKOUT_ADDRESS_LINE2`, `CHECKOUT_CITY`, `CHECKOUT_STATE`, `CHECKOUT_ZIP`, `CHECKOUT_COUNTRY`, `CHECKOUT_PHONE`) in the CredentialStore; no card number or CVV is stored
- 2. BestBuy and Amazon plugins fill the shipping form fields from the `CheckoutProfile` loaded at plugin setup time; CVV is provided via `getpass` at runtime only
- 3. A CI grep assertion confirms no `_cvv` value appears in any `writeLog()` call argument on checkout code paths
- 4. If a shipping form field selector returns None (DOM drift), the plugin logs a WARNING with the selector name and returns False without submitting an incomplete form
-
-**Plans**: 4 plans
-
-Plans:
-
-**Wave 1** *(parallel-safe; no file overlap)*
-
-- [x] 20-01-PLAN.md — core/checkout_profile.py: CHECKOUT_PROFILE_KEYS (9, NOT in SECRET_KEYS) + CheckoutProfile model + load_checkout_profile() incomplete detection + tests (BUY-07)
-- [x] 20-02-PLAN.md — `setup checkout-profile` CLI: visible-prompt 9 keys, key-NAME-only output, optional ADDRESS_LINE2, no card/CVV stored (BUY-07)
-- [x] 20-03-PLAN.md — CVV threading: Amazon __init__ _cvv=None + orchestrator amz injection (mirrors BestBuy 411-414) + run.py needs_cvv includes amazon.com (BUY-07)
-
-**Wave 2** *(blocked on 20-01 + 20-03)*
-
-- [x] 20-04-PLAN.md — Form-fill: _checkout_profile load at setup() + _fill_field + BestBuy shipping fill before place_order_guarded (missing-selector WARN+False) + Amazon CVV skip-if-absent + CVV-not-in-logs AST test (BUY-07)
-
-### Phase 21: Per-Step Timeouts + Unified Retry + Cart-Retry
-
-**Goal**: Checkout attempts are bounded in time and retries, and all retry/backoff logic flows through one shared `RetryPolicy` so supervisor restarts and cart retries cannot compound into a runaway loop
-**Depends on**: Phase 18 (CheckoutConfig fields), Phase 19 (DB `order_id` idempotency anchor must exist before retry reads it)
-**Requirements**: BUY-05, BUY-06, REL-08
-**Success Criteria** (what must be TRUE):
-
- 1. `core/retry.py` contains a single `RetryPolicy` dataclass and `with_retry()` async helper; both supervisor restart (Phase 22) and cart-retry use this module; a CI grep assertion confirms no standalone `for attempt in range(N)` retry loops exist outside `core/retry.py`
- 2. Each checkout DOM step (navigate, add-to-cart, checkout-proceed, CVV entry, place-order click, confirmation wait) runs under its own `asyncio.timeout(step_timeout_secs)` context manager; the entire `auto_buy()` method is NOT wrapped in a single outer timeout
- 3. A `checkout_stage` variable tracks progress within `auto_buy()`; on `CancelledError` the stage is logged for post-mortem and the item is not immediately re-submitted
- 4. Cart-retry reads the DB `order_id` column before each attempt; if a prior attempt already wrote a confirmed order, the retry loop exits immediately without re-submitting
- 5. Cart-retry is bounded by `CheckoutConfig.max_cart_retries` (default 3) with exponential backoff; the retry never re-enters the place-order click stage on an already-attempted order
-
-**Plans**: 4 plans
-
-Plans:
-
-**Wave 1** *(parallel-safe; no file overlap)*
-
-- [x] 21-01-PLAN.md — core/retry.py NEW: RetryPolicy dataclass + pure compute_delay (seedable jitter) + with_retry async helper; tests/test_no_retry_loops.py AST guard (no `for attempt in range(` outside core/retry.py) (REL-08)
-- [x] 21-02-PLAN.md — models.py: increment_checkout_attempts_sync (+1 per call) + get_item_order_state_sync (purchased, order_id idempotency anchor); no schema change (BUY-05)
-- [x] 21-03-PLAN.md — per-step asyncio.timeout: self._checkout_stage default on RetailerPlugin ABC + 6 Amazon / 8 BestBuy DOM stages each under their own asyncio.timeout(step_timeout_secs); no outer timeout; per-item ceiling deferred to P22 (BUY-06)
-
-**Wave 2** *(blocked on 21-01 + 21-02 + 21-03)*
-
-- [x] 21-04-PLAN.md — orchestrator cart-retry: extract _attempt_buy(plugin, link)->(bool, str|None) + with_retry/RetryPolicy from CheckoutConfig; DB re-read + checkout_attempts increment before each attempt; confirmed order_id short-circuit (no double-buy); single enqueue outside loop (BUY-05, REL-08)
-
-**Research flag** (RESOLVED via 21-CONTEXT.md + 21-RESEARCH.md): the `checkout_attempts` increment strategy is resolved — increment once per retry ATTEMPT, BEFORE each attempt (not per-cart-add, not confirmed-only). max_cart_retries semantics documented: it is the number of RETRIES after the first attempt, so total attempts = 1 + max_cart_retries (max_cart_retries=0 → single attempt, no retry).
-
-### Phase 22: Supervisor + Browser Relaunch + Server Safety
-
-**Goal**: A single plugin crash or browser death cannot take down the rest of the bot; plugins restart automatically with backoff and full stealth/proxy/login restoration; the bot shuts down cleanly on SIGTERM
-**Depends on**: Phase 18 (CheckoutConfig for alert_on_errors), Phase 21 (RetryPolicy from core/retry.py; supervisor reuses it)
-**Requirements**: REL-01, REL-02, REL-03, REL-05, REL-06, SRV-02
-**Success Criteria** (what must be TRUE):
-
- 1. An unhandled exception in one plugin's `run_plugin` coroutine is absorbed by the supervisor before the `asyncio.TaskGroup` boundary; all other plugin coroutines keep running (verified by a test that crashes one plugin and asserts others continue)
- 2. A plugin that exceeds N failures within a time window is parked (no further restart attempts) and the operator receives a notification through the existing dispatcher
- 3. On a dead or disconnected Chrome process, the supervisor calls `plugin.relaunch()` which executes the full sequence: teardown, assign_proxy, setup (new browser + stealth + proxy auth), restore_session (no-op stub until Phase 23), login; `apply_stealth` is verified called on the relaunched browser
- 4. Transient `sqlite3.OperationalError` (locked DB) on any `run_in_executor` read path in `run_plugin` is caught and isolated: the poll cycle is skipped, not terminated
- 5. Each item's check/buy cycle runs under `asyncio.timeout(item_timeout_secs)`; the `write_queue.put()` calls are placed OUTSIDE the timeout context so a timed-out item cannot orphan a pending DB write
- 6. SIGTERM and SIGINT trigger cooperative teardown (write-queue flush + browser teardown) via a platform-appropriate signal bridge (`sys.platform` branch handles Windows `NotImplementedError` on `loop.add_signal_handler`)
-
-**Plans**: 4 plans
-
-Plans:
-
-**Wave 1** *(parallel-safe; disjoint file)*
-
-- [x] 22-01-PLAN.md — core/plugin_base.py: concrete relaunch() (teardown→setup→restore_session→login; stealth re-injected via setup) + restore_session() no-op stub returns False; PLUGIN_API_VERSION stays 2 (REL-03)
-
-**Wave 2** *(orchestrator.py edits serialize — same-file, no parallel)*
-
-- [x] 22-02-PLAN.md — run_plugin hardening: cfg param + sqlite3.OperationalError read isolation on items read + per-item asyncio.timeout(item_timeout_secs); write_queue.put stays outside (REL-05, REL-06)
-
-**Wave 3** *(blocked on 22-01 + 22-02)*
-
-- [x] 22-03-PLAN.md — supervise() wrapper + _is_browser_dead_exc + failure budget (alert_on_errors / 600s deque) + park notify + browser-dead assign_proxy→relaunch + async_main create_task wiring (REL-01, REL-02)
-
-**Wave 4** *(blocked on 22-03)*
-
-- [x] 22-04-PLAN.md — _register_signals (POSIX add_signal_handler / Windows signal.signal fallback) + _flush_write_queue manual drain + async_main signal registration + pre-teardown flush (SRV-02)
-
-**Research flag** (RESOLVED via 22-RESEARCH.md): the nodriver stealth-persistence question is answered — `add_script_to_evaluate_on_new_document` is a per-session CDP command that is NOT persisted across `Browser.stop()` + `Browser.create()`; every plugin’s setup() re-injects stealth via apply_stealth, so relaunch() calling setup() is sufficient (HIGH confidence, confirmed against installed nodriver 0.50.3 source).
-
-### Phase 23: Encrypted Session Persistence
-
-**Goal**: Users can opt into encrypted cookie persistence so the bot skips re-login and MFA across restarts without storing any auth material in plaintext
-**Depends on**: Phase 22 (supervisor calls `restore_session` on relaunch; Phase 22 ships a no-op stub that this phase replaces)
-**Requirements**: REL-04
-**Success Criteria** (what must be TRUE):
-
- 1. When `platforms..session_persistence: true`, cookies are saved after successful login via `core/session_store.py` using Fernet encryption (same scrypt KDF as `EncryptedFileBackend`); no plaintext `.json` or `.pickle` file is written to disk
- 2. On bot restart or plugin relaunch, cookies are restored via raw CDP `cdp.storage.set_cookies()` (bypassing the confirmed `CookieJar.set_all()` bug); the restore path returns `True` if a session file existed, `False` otherwise (no crash if file is absent)
- 3. The `data/sessions/` directory is in `.gitignore`; a CI check confirms no session file pattern matches are committed
- 4. The Phase 22 no-op `restore_session` stub in `plugin.relaunch()` is replaced by the live `SessionStore.restore()` call
-
-**Plans**: TBD
-**Research flag**: NEEDS PLAN-PHASE RESEARCH — `nodriver cdp.storage.set_cookies()` exact import path and `CookieParam` constructor signature should be verified against the installed `nodriver==0.50.3` package before implementing the restore path. The workaround is confirmed from issues #1816/#2020 but the exact API shape needs local verification.
-
-### Phase 24: Health Surface + Server Safety
-
-**Goal**: Operators can query per-plugin liveness and error state from the CLI or web UI, receive alerts when a plugin degrades, and run the bot headlessly on a server without a pygame import crash
-**Depends on**: Phases 22 and 23 (health surface reads from supervisor + session state; SRV-01 is self-contained but grouped here as a low-dependency finish item)
-**Requirements**: REL-07, SRV-01
-**Success Criteria** (what must be TRUE):
-
- 1. `BotService.get_status()` returns a structured dict including `{"running": bool, "uptime_secs": float, "plugins": {name: {"status": str, "last_heartbeat": float, "consecutive_errors": int, "items_checked": int, "orders_confirmed": int}}}` queryable from CLI (`shoppybot status`) and the FastAPI `/status` endpoint
- 2. When a plugin's `consecutive_errors` exceeds the configured `alert_on_errors` threshold, a `health_degraded` event is dispatched through the existing notification fan-out channels
- 3. On a headless host with no audio device, the sound notifier degrades to a silent no-op at import time rather than crashing; the bot starts and runs normally without pygame
-
-**Plans**: 5 plans
-
-Plans:
-
-**Wave 1** *(parallel-safe; disjoint files)*
-
-- [x] 24-01-PLAN.md — core/health.py HealthRegistry (per-plugin status/heartbeat/consecutive_errors/items_checked/orders_confirmed) + JSON-safe snapshot + in-memory armed/disarmed dedup + tests (REL-07)
-- [x] 24-02-PLAN.md — utils.py pygame headless guard: _initialize_audio()/_AUDIO_AVAILABLE, play_sound no-op when no device, redundant mixer.init removed + tests (SRV-01)
+
+✅ v4.0 Win-the-Drop (Phases 18-24) — SHIPPED 2026-06-25
-**Wave 2** *(blocked on 24-01; disjoint files service/orchestrator/cli)*
+- [x] Phase 18: Safety Gate + Config Foundation (4/4 plans) — 2026-06-11
+- [x] Phase 19: DB Schema + Confirmation Detection (4/4 plans) — 2026-06-11
+- [x] Phase 20: Checkout Profile + Form-Fill (4/4 plans) — 2026-06-11
+- [x] Phase 21: Per-Step Timeouts + Unified Retry + Cart-Retry (4/4 plans) — 2026-06-12
+- [x] Phase 22: Supervisor + Browser Relaunch + Server Safety (4/4 plans) — 2026-06-12
+- [x] Phase 23: Encrypted Session Persistence (4/4 plans) — 2026-06-12
+- [x] Phase 24: Health Surface + Server Safety (5/5 plans) — 2026-06-12
-- [x] 24-03-PLAN.md — core/service.py get_status() locked shape {running,uptime_secs,plugins{...}} + _start_time uptime + single HealthRegistry ref wired into async_main; /status endpoint unchanged + JSON-serializable test (REL-07)
-- [x] 24-04-PLAN.md — core/orchestrator.py wiring: health=None kwarg through async_main/supervise/run_plugin; heartbeat/items_checked/status transitions/orders_confirmed; health_degraded fire-once+re-arm dedup via existing dispatcher, distinct from plugin_parked + tests (REL-07)
-- [x] 24-05-PLAN.md — core/cli/status.py shoppybot status subcommand (per-plugin table + --json, no network, in-process-state note) + subparser registration + tests (REL-07)
+Full phase detail archived at `.planning/milestones/v4.0-ROADMAP.md`.
+Audit: `.planning/milestones/v4.0-MILESTONE-AUDIT.md` (status: tech_debt — pre-accepted live-UAT debt).
-**UI hint**: yes
+
---
## Progress
-| Phase | Milestone | Plans | Status | Completed |
-|-------|-----------|-------|--------|-----------|
-| 1. Foundations + Security | v1 | 5/5 | Complete | 2026-06-02 |
-| 2. Plugin Migration | v1 | 6/6 | Complete | 2026-06-03 |
-| 3. Community Documentation | v1 | 2/2 | Complete | 2026-06-03 |
-| 4. Async Orchestrator | v1 | 5/5 | Complete | 2026-06-03 |
-| 5. Notification System | v1 | 5/5 | Complete | 2026-06-03 |
-| 6. Platform Expansion | v1 | 5/5 | Complete | 2026-06-03 |
-| 7. Modular Core Service | v2.0 | 3/3 | Complete | 2026-06-04 |
-| 8. Credential Store | v2.0 | 4/4 | Complete | 2026-06-04 |
-| 9. CLI Front-End | v2.0 | 4/4 | Complete | 2026-06-04 |
-| 10. Optional Web UI | v2.0 | 4/4 | Complete | 2026-06-04 |
-| 11. Cross-Platform Verification | v2.0 | 5/5 | Complete | 2026-06-05 |
-| 12. Stability Foundation | v3.0 | 4/4 | Complete | 2026-06-09 |
-| 13. Anti-Detection Layer 1 — Fingerprint + Proxy | v3.0 | 3/3 | Complete | 2026-06-09 |
-| 14. Anti-Detection Layer 2 — CAPTCHA Solving | v3.0 | 3/3 | Complete | 2026-06-09 |
-| 15. Plugin Ecosystem Registry | v3.0 | 3/3 | Complete | 2026-06-09 |
-| 16. Price Monitoring | v3.0 | 4/4 | Complete | 2026-06-10 |
-| 17. Test Hardening | v3.0 | 4/4 | Complete | 2026-06-10 |
-| 18. Safety Gate + Config Foundation | v4.0 | 4/4 | Complete | 2026-06-11 |
-| 19. DB Schema + Confirmation Detection | v4.0 | 4/4 | Complete | 2026-06-11 |
-| 20. Checkout Profile + Form-Fill | v4.0 | 4/4 | Complete | 2026-06-11 |
-| 21. Per-Step Timeouts + Unified Retry + Cart-Retry | v4.0 | 4/4 | Complete | 2026-06-12 |
-| 22. Supervisor + Browser Relaunch + Server Safety | v4.0 | 4/4 | Complete | 2026-06-12 |
-| 23. Encrypted Session Persistence | v4.0 | 4/4 | Complete | 2026-06-12 |
-| 24. Health Surface + Server Safety | v4.0 | 5/5 | Complete | 2026-06-12 |
+| Milestone | Phases | Plans | Status | Shipped |
+|-----------|--------|-------|--------|---------|
+| v1 Open Source Launch | 1-6 | 28/28 | ✅ Shipped | 2026-06-03 |
+| v2.0 Modular Core + Cross-Platform UX | 7-11 | 20/20 | ✅ Shipped | 2026-06-06 |
+| v3.0 Resilience + Ecosystem | 12-17 | 21/21 | ✅ Shipped | 2026-06-10 |
+| v4.0 Win-the-Drop | 18-24 | 29/29 | ✅ Shipped | 2026-06-25 |
-All 66 v1+v2.0 requirements satisfied. v3.0: 18 requirements mapped across Phases 12-17. v4.0: 17 requirements mapped across Phases 18-24.
+All requirements satisfied across v1 (44) + v2.0 (22) + v3.0 (18) + v4.0 (17). Per-milestone requirement detail in `.planning/milestones/v*-REQUIREMENTS.md`.
---
-*Last updated: 2026-06-12 — Phase 24 planned (5 plans, 2 waves)*
+*Last updated: 2026-06-25 — v4.0 Win-the-Drop shipped; ready for next milestone (`/gsd:new-milestone`).*
diff --git a/.planning/STATE.md b/.planning/STATE.md
index 2fb9cb6..05bd245 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -2,15 +2,15 @@
gsd_state_version: 1.0
milestone: v4.0
milestone_name: Win-the-Drop
-status: verifying
-last_updated: "2026-06-12T21:27:47.343Z"
-last_activity: 2026-06-12
+status: Awaiting next milestone
+last_updated: "2026-06-25T01:32:12.122Z"
+last_activity: 2026-06-25 — Milestone v4.0 completed and archived
progress:
- total_phases: 13
+ total_phases: 7
completed_phases: 7
total_plans: 29
completed_plans: 29
- percent: 54
+ percent: 100
---
# ShopPyBot — State
@@ -28,30 +28,30 @@ progress:
## Current Position
-Phase: 24
-Plan: Not started
-Status: Phase complete — ready for verification
-Last activity: 2026-06-12
+Phase: Milestone v4.0 complete
+Plan: —
+Status: Awaiting next milestone
+Last activity: 2026-06-25 — Milestone v4.0 completed and archived
## Phase Status
| Phase | Goal Summary | Status | Reqs |
|-------|-------------|--------|------|
-| 18 — Safety Gate + Config Foundation | monitor-only mode + place_order_guarded() ABC + CheckoutConfig schema | Not started | BUY-01, BUY-02 |
-| 19 — DB Schema + Confirmation Detection | order_id/confirmed_at columns + core/confirmation.py; purchased only on real order | Not started | BUY-03, BUY-04 |
-| 20 — Checkout Profile + Form-Fill | 9-key CredentialStore profile; BestBuy + Amazon form-fill; CVV getpass-only | Not started | BUY-07 |
-| 21 — Per-Step Timeouts + Unified Retry + Cart-Retry | core/retry.py RetryPolicy; per-step asyncio.timeout; idempotent cart-retry | Not started | BUY-05, BUY-06, REL-08 |
+| 18 — Safety Gate + Config Foundation | monitor-only mode + place_order_guarded() ABC + CheckoutConfig schema | Complete | BUY-01, BUY-02 |
+| 19 — DB Schema + Confirmation Detection | order_id/confirmed_at columns + core/confirmation.py; purchased only on real order | Complete | BUY-03, BUY-04 |
+| 20 — Checkout Profile + Form-Fill | 9-key CredentialStore profile; BestBuy + Amazon form-fill; CVV getpass-only | Complete | BUY-07 |
+| 21 — Per-Step Timeouts + Unified Retry + Cart-Retry | core/retry.py RetryPolicy; per-step asyncio.timeout; idempotent cart-retry | Complete | BUY-05, BUY-06, REL-08 |
| 22 — Supervisor + Browser Relaunch + Server Safety | per-coroutine supervision; failure budget; full relaunch sequence; DB read isolation; SIGTERM bridge | Complete | REL-01, REL-02, REL-03, REL-05, REL-06, SRV-02 |
-| 23 — Encrypted Session Persistence | core/session_store.py Fernet cookies; CDP restore path; replaces Phase 22 stub | In Progress (1/4 plans) | REL-04 |
-| 24 — Health Surface + Server Safety | core/health.py HealthRegistry; get_status() expansion; health_degraded alert; pygame headless guard | Not started | REL-07, SRV-01 |
+| 23 — Encrypted Session Persistence | core/session_store.py Fernet cookies; CDP restore path; replaces Phase 22 stub | Complete | REL-04 |
+| 24 — Health Surface + Server Safety | core/health.py HealthRegistry; get_status() expansion; health_degraded alert; pygame headless guard | Complete | REL-07, SRV-01 |
---
## Performance Metrics
-**Plans completed**: 20 of 20
-**Requirements completed**: SRV-02 (plus all prior phases)
-**Phases completed**: 5 of 7
+**Plans completed**: 29 of 29
+**Requirements completed**: 17 of 17 (BUY-01..07, REL-01..08, SRV-01, SRV-02)
+**Phases completed**: 7 of 7
**Blockers resolved**: 0
---
@@ -93,7 +93,8 @@ Last activity: 2026-06-12
### Active Todos
-- Run `/gsd:plan-phase 18` to begin Phase 18 planning
+- v4.0 complete + archived. Run `/gsd:new-milestone` to scope the next cycle (phase numbering continues from 24).
+- Operator: work the v4.0 live-UAT checklist (Deferred Items below) before the first production live-buy.
### Blockers
@@ -115,6 +116,26 @@ Items acknowledged and deferred at v2.0 milestone close on 2026-06-05. All are l
| uat | Phase 01 — 01-UAT.md | partial (0 pending) |
| uat | Phase 19 — per-retailer confirmation selectors (Amazon + BestBuy) | UAT required before Phase 19 finalizes selectors |
+### Acknowledged at v4.0 milestone close (2026-06-25) — 17 items
+
+All deferred per the autonomous live-UAT policy; none are code gaps. This is the operator's pre-production live-buy checklist. Source: `gsd-sdk query audit-open` at close.
+
+| Category | Item | Status |
+|----------|------|--------|
+| uat | Phase 18 — live `--monitor-only` run fires alerts but places no order (18-HUMAN-UAT.md) | partial (1 pending) |
+| uat | Phase 19 — live Amazon/BestBuy confirmation URL + DOM order-number selectors (19-HUMAN-UAT.md) | partial (3 pending) |
+| uat | Phase 20 — live BestBuy/Amazon shipping form-fill selectors + CVV entry (20-HUMAN-UAT.md) | partial (4 pending) |
+| uat | Phase 21 — per-step timeout clean-abort under a real slow drop (21-HUMAN-UAT.md) | partial (2 pending) |
+| uat | Phase 22 — live supervisor restart + browser relaunch + SIGTERM teardown (22-HUMAN-UAT.md) | partial (2 pending) |
+| uat | Phase 23 — live cross-restart MFA/login-skip; persisted session accepted (23-HUMAN-UAT.md) | partial (2 pending) |
+| uat | Phase 24 — live headless-server run, no audio device / pygame absent (24-HUMAN-UAT.md) | partial (1 pending) |
+| verification | Phases 18-24 — VERIFICATION.md status `human_needed` (automated must-haves passed; live checks deferred) | human_needed (7) |
+| todo | Amazon WAF CAPTCHA auto-solve wiring (waf-auto-solve-followup.md) | pending (medium); manual-pause fallback in place |
+| seed | SEED-001 — public repo history scrub/squash before release | dormant (release milestone) |
+| seed | SEED-002 — release-please automatic version tagging | dormant (release milestone) |
+
+**Tracked HIGH item (from audit):** Phase 21 place-order-stage timeout double-buy edge (placed-but-unconfirmed) — verify live and consider P22-style hardening.
+
---
| Phase 18 P02 | 267 | 2 tasks | 2 files |
| Phase 18 P18-03 | 8m | 2 tasks | 6 files |
@@ -335,4 +356,4 @@ Items acknowledged and deferred at v2.0 milestone close on 2026-06-05. All are l
## Operator Next Steps
-- Run `/gsd:plan-phase 18` to begin Phase 18 (Safety Gate + Config Foundation)
+- Start the next milestone with /gsd:new-milestone
diff --git a/.planning/v4.0-MILESTONE-AUDIT.md b/.planning/milestones/v4.0-MILESTONE-AUDIT.md
similarity index 100%
rename from .planning/v4.0-MILESTONE-AUDIT.md
rename to .planning/milestones/v4.0-MILESTONE-AUDIT.md
diff --git a/.planning/REQUIREMENTS.md b/.planning/milestones/v4.0-REQUIREMENTS.md
similarity index 97%
rename from .planning/REQUIREMENTS.md
rename to .planning/milestones/v4.0-REQUIREMENTS.md
index a495073..e513e58 100644
--- a/.planning/REQUIREMENTS.md
+++ b/.planning/milestones/v4.0-REQUIREMENTS.md
@@ -1,3 +1,12 @@
+# Requirements Archive: v4.0 Win-the-Drop
+
+**Archived:** 2026-06-25
+**Status:** SHIPPED
+
+For current requirements, see `.planning/REQUIREMENTS.md`.
+
+---
+
# ShopPyBot — Requirements
**Current Milestone:** v4.0 Win-the-Drop (Acquisition Core + Reliability)
diff --git a/.planning/milestones/v4.0-ROADMAP.md b/.planning/milestones/v4.0-ROADMAP.md
new file mode 100644
index 0000000..bca98da
--- /dev/null
+++ b/.planning/milestones/v4.0-ROADMAP.md
@@ -0,0 +1,427 @@
+# ShopPyBot — Roadmap
+
+## Project
+
+**Core Value:** A drop-in plugin framework that lets the community add new retail platform integrations by placing a single Python file in `plugins/` — no core changes required.
+
+---
+
+## Milestones
+
+- ✅ **v1 Open Source Launch** — Phases 1-6 (shipped 2026-06-03)
+- ✅ **v2.0 Modular Core + Cross-Platform UX** — Phases 7-11 (shipped 2026-06-06)
+- ✅ **v3.0 Resilience + Ecosystem** — Phases 12-17 (shipped 2026-06-10)
+- **v4.0 Win-the-Drop (Acquisition Core + Reliability)** — Phases 18-24 (in progress)
+
+---
+
+## Phases
+
+
+✅ v1 Open Source Launch (Phases 1-6) — SHIPPED 2026-06-03
+
+- [x] Phase 1: Foundations + Security (5/5 plans) — 2026-06-02
+- [x] Phase 2: Plugin Migration (6/6 plans) — 2026-06-03
+- [x] Phase 3: Community Documentation (2/2 plans) — 2026-06-03
+- [x] Phase 4: Async Orchestrator (5/5 plans) — 2026-06-03
+- [x] Phase 5: Notification System (5/5 plans) — 2026-06-03
+- [x] Phase 6: Platform Expansion (5/5 plans) — 2026-06-03
+
+
+
+
+✅ v2.0 Modular Core + Cross-Platform UX (Phases 7-11) — SHIPPED 2026-06-06
+
+- [x] Phase 7: Modular Core Service (3/3 plans) — 2026-06-04
+- [x] Phase 8: Credential Store (4/4 plans) — 2026-06-04
+- [x] Phase 9: CLI Front-End (4/4 plans) — 2026-06-04
+- [x] Phase 10: Optional Web UI (4/4 plans) — 2026-06-04
+- [x] Phase 11: Cross-Platform Verification (5/5 plans) — 2026-06-05
+
+Full phase detail archived at `.planning/milestones/v2.0-ROADMAP.md`.
+Audit: `.planning/milestones/v2.0-MILESTONE-AUDIT.md` (status: passed).
+
+
+
+
+✅ v3.0 Resilience + Ecosystem (Phases 12-17) — SHIPPED 2026-06-10
+
+- [x] **Phase 12: Stability Foundation** — Close v2.0 deferred cross-OS checks and resolve 4 audit tech-debt items (completed 2026-06-09)
+- [x] **Phase 13: Anti-Detection Layer 1 — Fingerprint + Proxy** — Apply JS fingerprint stealth patch and implement proxy rotation with ban detection (completed 2026-06-09)
+- [x] **Phase 14: Anti-Detection Layer 2 — CAPTCHA Solving** — Integrate 2captcha opt-in solver with CredentialStore key, startup balance check, async executor wrapping, and spend cap (completed 2026-06-09)
+- [x] **Phase 15: Plugin Ecosystem Registry** — Add difficulty/proxy/captcha class attrs to ABC, create GitHub wiki registry table, ship `shoppybot plugins list` command (completed 2026-06-09)
+- [x] **Phase 16: Price Monitoring** — Per-item target price, append-only price history table, percentage-drop trigger, fan-out price-drop alerts, price-history CLI (completed 2026-06-10)
+- [x] **Phase 17: Test Hardening** — Unit and integration coverage for all v3.0 features (completed 2026-06-10)
+
+Full phase detail archived in `.planning/milestones/v3.0-ROADMAP.md`.
+
+
+
+### v4.0 Win-the-Drop (Phases 18-24)
+
+- [x] **Phase 18: Safety Gate + Config Foundation** — Central monitor-only mode, `place_order_guarded()` ABC method closing the 6-of-7 plugin safety hole, and `CheckoutConfig` schema as the foundation every downstream phase depends on (completed 2026-06-11)
+- [x] **Phase 19: DB Schema + Confirmation Detection** — Add `order_id`/`confirmed_at`/`checkout_attempts` columns and build `core/confirmation.py` so `purchased` is only written on a real confirmed order number, never on a button click (completed 2026-06-11)
+- [x] **Phase 20: Checkout Profile + Form-Fill** — Shipping/billing profile stored in CredentialStore (9 keys, no card data), BestBuy and Amazon form-fill, CVV getpass-only at runtime (completed 2026-06-11)
+- [x] **Phase 21: Per-Step Timeouts + Unified Retry + Cart-Retry** — One `RetryPolicy` in `core/retry.py` shared by both supervisor restart and cart-retry; per-step `asyncio.timeout()` per DOM stage; idempotency guard reads DB before every attempt (completed 2026-06-12)
+- [x] **Phase 22: Supervisor + Browser Relaunch + Server Safety** — Per-coroutine supervision with failure budget absorbs crashes before the TaskGroup boundary; full relaunch sequence (teardown, proxy, stealth, login); DB read isolation; per-item orchestrator timeout; SIGTERM/SIGINT teardown bridge (completed 2026-06-12)
+- [x] **Phase 23: Encrypted Session Persistence** — Fernet-encrypted cookie save/restore via `core/session_store.py` (reuses `EncryptedFileBackend` pattern); raw CDP restore path that bypasses the confirmed `set_all()` bug; replaces Phase 22's no-op stub (completed 2026-06-12)
+- [x] **Phase 24: Health Surface + Server Safety** — `core/health.py` HealthRegistry, expanded `BotService.get_status()` with per-plugin liveness/heartbeat, `health_degraded` notification event, updated FastAPI `/status` endpoint, headless pygame crash guard (completed 2026-06-12)
+
+---
+
+## Phase Details
+
+### Phase 12: Stability Foundation
+
+**Goal**: The v2.0 deferred debt and audit tech-debt are paid down before new features land, so the test suite is a reliable baseline
+**Depends on**: Nothing — do first
+**Requirements**: STAB-01, STAB-02
+**Success Criteria** (what must be TRUE):
+
+ 1. All 4 deferred v2.0 cross-OS/UI manual checks (keyring restart survival, masked-TTY passphrase prompt, web dashboard render on Ubuntu, `0.0.0.0` bind warning) are executed and documented pass or fail, with any failures fixed
+ 2. Each of the 4 v2.0 audit tech-debt items has a targeted regression test that passes in CI
+ 3. No broad refactors occur: only the specific items in scope are changed
+
+**Plans**: 4 plans
+
+Plans:
+
+- [x] 12-01-PLAN.md — TD-1: re-anchor logger logging_level read to core.paths.config_path() + regression test
+- [x] 12-02-PLAN.md — TD-2/TD-3: harden SC1 secret-read guard (rglob) and separator guard (__file__-anchored)
+- [x] 12-03-PLAN.md — TD-4 config write-seam regression test + accepted MOD-02 gap doc; MC-4 0.0.0.0 banner assertion
+- [x] 12-04-PLAN.md — Execute and document MC-1..MC-4 deferred manual checks in docs/PLATFORMS.md
+
+### Phase 13: Anti-Detection Layer 1 — Fingerprint + Proxy
+
+**Goal**: Users can enable proxy rotation and the bot applies a JS fingerprint stealth patch at browser startup, measurably reducing Layer 2 bot signals
+**Depends on**: Phase 12
+**Requirements**: ANTI-08, ANTI-04, ANTI-05
+**Success Criteria** (what must be TRUE):
+
+ 1. Bot applies `window.chrome`, `navigator.plugins`, `navigator.languages`, and screen-dimension patches via `core/stealth.py` at every browser startup with no plugin ABC version bump
+ 2. User can enable proxy rotation via an opt-in `proxy:` config section (disabled by default) listing `scheme://host:port` URLs; bot logs "Proxy rotation: enabled, pool_size=N" at startup
+ 3. Bot detects ban signals (HTTP 403/429/503, challenge-redirect, block-phrase body) and rotates to the next proxy, retiring a proxy after N consecutive failures for a configurable cooldown period
+ 4. WebRTC Chrome preferences are set at browser launch to prevent real-IP leaks through the proxy tunnel
+ 5. Each proxy is scoped to its plugin instance (`self._proxy`) and rotated only at browser restart, not mid-session
+
+**Plans**: 3 plans
+
+Plans:
+
+- [x] 13-01-PLAN.md — core/stealth.py: STEALTH_JS + apply_stealth, ProxyPool (round-robin/retire/cooldown), proxy launch args + WebRTC flag, CDP Fetch auth, ban-signal detector (+ unit tests)
+- [x] 13-02-PLAN.md — ProxyConfig schema (opt-in, disabled by default) + documented sample.config.yml proxy section
+- [x] 13-03-PLAN.md — Wire stealth + proxy into BotService/orchestrator/registry and all 8 plugins; per-instance scoping, exact startup log, fail-loud on pool exhaustion, ban-detect recording
+
+**UI hint**: no
+
+### Phase 14: Anti-Detection Layer 2 — CAPTCHA Solving
+
+**Goal**: Users who encounter reCAPTCHA v2 or Amazon WAF CAPTCHAs can opt into automated solving via 2captcha with full cost visibility and no credential plaintext exposure
+**Depends on**: Phase 13 (fingerprint + proxy layer in place before adding CAPTCHA layer)
+**Requirements**: ANTI-06, ANTI-07
+**Success Criteria** (what must be TRUE):
+
+ 1. User can enable CAPTCHA solving via `captcha.enabled: true` in config; the 2captcha API key is stored exclusively in CredentialStore (`TWOCAPTCHA_API_KEY`), never in config.yml
+ 2. Bot checks 2captcha account balance at startup, logs a WARNING when balance is low, and skips solver use (falling back to manual pause) when balance is zero
+ 3. CAPTCHA solve calls use `run_in_executor` + `asyncio.timeout(120)` so other plugin poll tasks are not blocked during a solve
+ 4. A configurable `captcha.max_solves_per_run` limit prevents unbounded API charges; default config disables CAPTCHA solving
+
+**Plans**: 3 plans
+
+Plans:
+
+- [x] 14-01-PLAN.md — Foundation: TWOCAPTCHA_API_KEY in SECRET_KEYS, CaptchaConfig, CaptchaSolver 2captcha v1 client (submit/poll/balance/cap)
+- [x] 14-02-PLAN.md — Wiring: solver constructed fresh in async_main + startup balance check + registry.assign_solver (mirrors ProxyPool)
+- [x] 14-03-PLAN.md — Plugin solve path: Amazon + BestBuy reCAPTCHA solve under run_in_executor+timeout(120) with manual-pause fallback; WAF deferred
+
+### Phase 15: Plugin Ecosystem Registry
+
+**Goal**: Community contributors have a discoverable registry with clear difficulty ratings, and users can inspect loaded plugins locally without a network call
+**Depends on**: Phase 12
+**Requirements**: REG-01, REG-02, REG-03, REG-04
+**Success Criteria** (what must be TRUE):
+
+ 1. Plugin authors can declare `difficulty`, `requires_proxy`, and `requires_captcha` as class attributes on any plugin; existing plugins without these attrs continue to load with sensible defaults (non-breaking)
+ 2. The GitHub wiki registry table contains required fields for each community plugin: name, platform, domain patterns, maintainer, anti-detection difficulty, methods implemented, last-verified date, proxy-required, captcha-required
+ 3. Running `shoppybot plugins list` displays all locally loaded plugins with their declared domain patterns, difficulty, and proxy/captcha flags without making a network call
+ 4. CONTRIBUTING.md and the PR template require contributors to supply `difficulty`, `requires_proxy`, and `requires_captcha` for new plugin submissions
+
+**Plans**: 3 plans
+
+Plans:
+
+- [x] 15-01-PLAN.md — REG-02: add difficulty/requires_proxy/requires_captcha class attrs + __init_subclass__ difficulty validation to RetailerPlugin ABC (non-breaking, PLUGIN_API_VERSION stays 2) + test_plugin_base.py assertions
+- [x] 15-02-PLAN.md — REG-03: BotService.list_plugins() over registry._all_plugins + core/cli/plugins.py handler + plugins-list subparser with --json + no-network CLI tests
+- [x] 15-03-PLAN.md — REG-01/REG-04: docs/PLUGIN_REGISTRY.md 9-field wiki SPEC + CONTRIBUTING.md/PR-template/PLUGIN_DEV.md attr requirements + tests/test_docs.py
+
+### Phase 16: Price Monitoring
+
+**Goal**: Users can track per-item prices, receive fan-out alerts when prices drop to target or by a configured percentage, and inspect price history from the CLI
+**Depends on**: Phase 12
+**Requirements**: PRICE-01, PRICE-02, PRICE-03, PRICE-04, PRICE-05, PRICE-06
+**Success Criteria** (what must be TRUE):
+
+ 1. User can set `target_price` (absolute) and `price_drop_pct` (percentage) per item in config; NULL/absent means price monitoring is off for that item
+ 2. Bot records scraped prices in an append-only `price_history` SQLite table each poll cycle via an optional `get_price()` plugin ABC hook (default returns `None`); the DB migration is idempotent on existing installs
+ 3. Price-drop alerts are dispatched through the existing fan-out notification dispatcher using a distinct `price_drop` notification_type with dedup columns separate from stock alert columns
+ 4. Price alert payloads include the current price, target price, and percentage from target
+ 5. Running `shoppybot items price-history ` displays the last N recorded prices for that item
+
+**Plans**: 4 plans
+
+Plans:
+
+- [x] 16-01-PLAN.md — Data layer: idempotent price_history table + 4 items columns + 8 parameterized price _sync functions + ItemConfig target_price/price_drop_pct (PRICE-01/02/05)
+- [x] 16-02-PLAN.md — Plugin + notification contract: NotificationEvent price fields, default-None get_price() ABC hook, real Amazon get_price() + text→cents parser, price_drop notifier branches (PRICE-02/04)
+- [x] 16-03-PLAN.md — Orchestrator wiring: _check_and_buy price path, both triggers with separate dedup, single price_drop dispatch, startup config seeding + BotService.get_price_history (PRICE-02/03/04/05)
+- [x] 16-04-PLAN.md — CLI: shoppybot items price-history leaf with --limit (default 10), $X.XX table, no network (PRICE-06)
+
+**UI hint**: yes
+
+### Phase 17: Test Hardening
+
+**Goal**: Every new v3.0 feature has unit and integration coverage so regressions are caught by CI before they reach users
+**Depends on**: Phases 13, 14, 15, 16 (tests validate the implemented features)
+**Requirements**: STAB-03
+**Success Criteria** (what must be TRUE):
+
+ 1. Unit tests cover proxy config parsing, ban-signal detection logic, per-instance proxy scoping, and cooldown/retire logic
+ 2. Unit tests cover CAPTCHA config parsing, balance-check behavior, executor wrapping, and spend-cap enforcement
+ 3. Unit tests cover price comparison threshold logic, `price_history` DB schema (including idempotent migration against a v2.0 DB fixture), and price-drop dedup separation from stock-alert dedup
+ 4. Integration tests cover the plugin ABC additions (`difficulty`, `requires_proxy`, `requires_captcha` defaults and overrides) and the `get_price()` hook being called alongside `check_availability`
+
+**Plans**: 4 plans
+
+Plans:
+
+- [x] 17-01-PLAN.md — Proxy coverage: PX-01..PX-06 (config validator, fetch-handler tasks, ProxyPool edges, registry routing/lifecycle isolation)
+- [x] 17-02-PLAN.md — CAPTCHA coverage: CP-01..CP-05 (poll/timeout errors, balance gate, solve_amazon_waf submit/poll/decode)
+- [x] 17-03-PLAN.md — Price + get_price integration: PR-01..PR-04 + AB-01, AB-02 (v2.0-schema migration fixture, trigger guards, get_price-alongside-check_availability)
+- [x] 17-04-PLAN.md — Plugin ABC: AB-03, AB-04 (metadata overrides + _handle_ban ban→proxy-cooldown bridge)
+
+---
+
+### Phase 18: Safety Gate + Config Foundation
+
+**Goal**: Every plugin routes its final place-order action through an ABC-enforced gate that honors monitor-only mode, so no plugin — current or future — can place a live order when monitoring is active
+**Depends on**: Nothing (do first; all downstream v4.0 phases depend on this config foundation)
+**Requirements**: BUY-01, BUY-02
+**Success Criteria** (what must be TRUE):
+
+ 1. User can start the bot with `--monitor-only` CLI flag or `debug.monitor_only: true` in config; bot performs stock checks and fires alerts but `auto_buy` is never called for any plugin
+ 2. All 7 bundled plugins route their place-order DOM click through `place_order_guarded()` on the RetailerPlugin ABC; a CI test with all 7 plugins and `monitor_only=True` asserts zero calls to the purchase write-queue
+ 3. BestBuy's confirmed `test_mode` gap is closed: `place_order.click()` is not called when `monitor_only=True` or `test_mode=True` on any plugin
+ 4. `CheckoutConfig` sub-model exists in `AppConfig` with `item_timeout_secs`, `step_timeout_secs`, `max_cart_retries`, `backoff_base`, `backoff_jitter`, and `alert_on_errors` fields so downstream phases can use them without schema changes
+
+**Plans**: 4 plans
+
+Plans:
+
+**Wave 1**
+
+- [x] 18-01-PLAN.md — DebugConfig.monitor_only + CheckoutConfig model + AppConfig wiring (BUY-01, BUY-02)
+- [x] 18-02-PLAN.md — place_order_guarded() concrete method on RetailerPlugin ABC + unit tests (BUY-02)
+
+**Wave 2** *(blocked on Wave 1 completion)*
+
+- [x] 18-03-PLAN.md — orchestrator monitor_only gate + --monitor-only CLI flag + CVV short-circuit + ALLOWLIST (BUY-01)
+- [x] 18-04-PLAN.md — reroute all 7 plugins through place_order_guarded + test_safety_gate.py (7-plugin zero-write + grep) (BUY-02)
+
+### Phase 19: DB Schema + Confirmation Detection
+
+**Goal**: Purchases are only recorded when the bot has verified a real order number from the retailer's confirmation page, never on a button click alone
+**Depends on**: Phase 18 (monitor-only gate must be live before any live checkout UAT of confirmation selectors)
+**Requirements**: BUY-03, BUY-04
+**Success Criteria** (what must be TRUE):
+
+ 1. The `items` table has `order_id TEXT`, `confirmed_at TEXT`, and `checkout_attempts INTEGER DEFAULT 0` columns added via idempotent `ALTER TABLE` (same pattern as v3.0 price columns); existing DB installs migrate without data loss
+ 2. After `auto_buy()` returns, the orchestrator calls `detect_order_confirmation(tab, platform)` and only enqueues `("confirmed", link, order_id, ts)` to the write-queue when a non-None order_id is returned; `purchased=1` is set only at that point
+ 3. When confirmation is not detected, the bot logs a WARNING and falls back to the legacy `purchased` write tag (no silent failure, no double-buy risk from the confirmation path itself)
+ 4. Each confirmed checkout writes `order_id` and `confirmed_at` to the DB, providing the idempotency anchor for retry (Phase 21) to read before any re-attempt
+
+**Plans**: 4 plans
+
+Plans:
+
+**Wave 1** *(parallel-safe; no file overlap)*
+
+- [x] 19-01-PLAN.md — models.py: 3 idempotent confirmation columns (order_id/confirmed_at/checkout_attempts DEFAULT 0, NOT incremented) + update_item_confirmed_sync (BUY-04)
+- [x] 19-02-PLAN.md — core/confirmation.py NEW: detect_order_confirmation (URL-first/DOM-backup map, ~3s settle, Amazon orderID URL-param parse, CONFIRMED- sentinel) + FakeTab tests (BUY-03)
+- [x] 19-03-PLAN.md — core/plugin_base.py: additive sync get_active_tab() ABC default (returns main_tab; PLUGIN_API_VERSION stays 2) (BUY-03)
+
+**Wave 2** *(blocked on Wave 1)*
+
+- [x] 19-04-PLAN.md — orchestrator wiring (_try_auto_buy detect + confirmed/legacy fallback outside any timeout; _dispatch_write confirmed branch) + Amazon/BestBuy _last_tab + get_active_tab override + orchestrator tests (BUY-03, BUY-04)
+
+**Research flag** (RESOLVED via 19-RESEARCH.md): per-retailer confirmation URL patterns (Amazon `/gp/buy/thankyou`, BestBuy `/checkout/r/thank-you`) are HIGH confidence and hardcoded in `core/confirmation.py`; backup DOM selectors (`#confirmedOrderId`, `.thank-you-order-number`) are MEDIUM confidence — live UAT on a test_mode buy is tracked as UAT debt (STATE.md Deferred Items).
+
+### Phase 20: Checkout Profile + Form-Fill
+
+**Goal**: Users can configure a shipping/billing profile that the bot fills during BestBuy and Amazon checkout, with payment using the retailer-saved method plus CVV entered at runtime and no full card data persisted anywhere
+**Depends on**: Phase 18 (monitor-only gate required before any form-fill can be tested live); Phase 19 (confirmation detection should precede form-fill so a completed form-fill can be confirmed)
+**Requirements**: BUY-07
+**Success Criteria** (what must be TRUE):
+
+ 1. User can run `shoppybot setup checkout-profile` to interactively populate 9 address keys (`CHECKOUT_FIRST_NAME`, `CHECKOUT_LAST_NAME`, `CHECKOUT_ADDRESS_LINE1`, `CHECKOUT_ADDRESS_LINE2`, `CHECKOUT_CITY`, `CHECKOUT_STATE`, `CHECKOUT_ZIP`, `CHECKOUT_COUNTRY`, `CHECKOUT_PHONE`) in the CredentialStore; no card number or CVV is stored
+ 2. BestBuy and Amazon plugins fill the shipping form fields from the `CheckoutProfile` loaded at plugin setup time; CVV is provided via `getpass` at runtime only
+ 3. A CI grep assertion confirms no `_cvv` value appears in any `writeLog()` call argument on checkout code paths
+ 4. If a shipping form field selector returns None (DOM drift), the plugin logs a WARNING with the selector name and returns False without submitting an incomplete form
+
+**Plans**: 4 plans
+
+Plans:
+
+**Wave 1** *(parallel-safe; no file overlap)*
+
+- [x] 20-01-PLAN.md — core/checkout_profile.py: CHECKOUT_PROFILE_KEYS (9, NOT in SECRET_KEYS) + CheckoutProfile model + load_checkout_profile() incomplete detection + tests (BUY-07)
+- [x] 20-02-PLAN.md — `setup checkout-profile` CLI: visible-prompt 9 keys, key-NAME-only output, optional ADDRESS_LINE2, no card/CVV stored (BUY-07)
+- [x] 20-03-PLAN.md — CVV threading: Amazon __init__ _cvv=None + orchestrator amz injection (mirrors BestBuy 411-414) + run.py needs_cvv includes amazon.com (BUY-07)
+
+**Wave 2** *(blocked on 20-01 + 20-03)*
+
+- [x] 20-04-PLAN.md — Form-fill: _checkout_profile load at setup() + _fill_field + BestBuy shipping fill before place_order_guarded (missing-selector WARN+False) + Amazon CVV skip-if-absent + CVV-not-in-logs AST test (BUY-07)
+
+### Phase 21: Per-Step Timeouts + Unified Retry + Cart-Retry
+
+**Goal**: Checkout attempts are bounded in time and retries, and all retry/backoff logic flows through one shared `RetryPolicy` so supervisor restarts and cart retries cannot compound into a runaway loop
+**Depends on**: Phase 18 (CheckoutConfig fields), Phase 19 (DB `order_id` idempotency anchor must exist before retry reads it)
+**Requirements**: BUY-05, BUY-06, REL-08
+**Success Criteria** (what must be TRUE):
+
+ 1. `core/retry.py` contains a single `RetryPolicy` dataclass and `with_retry()` async helper; both supervisor restart (Phase 22) and cart-retry use this module; a CI grep assertion confirms no standalone `for attempt in range(N)` retry loops exist outside `core/retry.py`
+ 2. Each checkout DOM step (navigate, add-to-cart, checkout-proceed, CVV entry, place-order click, confirmation wait) runs under its own `asyncio.timeout(step_timeout_secs)` context manager; the entire `auto_buy()` method is NOT wrapped in a single outer timeout
+ 3. A `checkout_stage` variable tracks progress within `auto_buy()`; on `CancelledError` the stage is logged for post-mortem and the item is not immediately re-submitted
+ 4. Cart-retry reads the DB `order_id` column before each attempt; if a prior attempt already wrote a confirmed order, the retry loop exits immediately without re-submitting
+ 5. Cart-retry is bounded by `CheckoutConfig.max_cart_retries` (default 3) with exponential backoff; the retry never re-enters the place-order click stage on an already-attempted order
+
+**Plans**: 4 plans
+
+Plans:
+
+**Wave 1** *(parallel-safe; no file overlap)*
+
+- [x] 21-01-PLAN.md — core/retry.py NEW: RetryPolicy dataclass + pure compute_delay (seedable jitter) + with_retry async helper; tests/test_no_retry_loops.py AST guard (no `for attempt in range(` outside core/retry.py) (REL-08)
+- [x] 21-02-PLAN.md — models.py: increment_checkout_attempts_sync (+1 per call) + get_item_order_state_sync (purchased, order_id idempotency anchor); no schema change (BUY-05)
+- [x] 21-03-PLAN.md — per-step asyncio.timeout: self._checkout_stage default on RetailerPlugin ABC + 6 Amazon / 8 BestBuy DOM stages each under their own asyncio.timeout(step_timeout_secs); no outer timeout; per-item ceiling deferred to P22 (BUY-06)
+
+**Wave 2** *(blocked on 21-01 + 21-02 + 21-03)*
+
+- [x] 21-04-PLAN.md — orchestrator cart-retry: extract _attempt_buy(plugin, link)->(bool, str|None) + with_retry/RetryPolicy from CheckoutConfig; DB re-read + checkout_attempts increment before each attempt; confirmed order_id short-circuit (no double-buy); single enqueue outside loop (BUY-05, REL-08)
+
+**Research flag** (RESOLVED via 21-CONTEXT.md + 21-RESEARCH.md): the `checkout_attempts` increment strategy is resolved — increment once per retry ATTEMPT, BEFORE each attempt (not per-cart-add, not confirmed-only). max_cart_retries semantics documented: it is the number of RETRIES after the first attempt, so total attempts = 1 + max_cart_retries (max_cart_retries=0 → single attempt, no retry).
+
+### Phase 22: Supervisor + Browser Relaunch + Server Safety
+
+**Goal**: A single plugin crash or browser death cannot take down the rest of the bot; plugins restart automatically with backoff and full stealth/proxy/login restoration; the bot shuts down cleanly on SIGTERM
+**Depends on**: Phase 18 (CheckoutConfig for alert_on_errors), Phase 21 (RetryPolicy from core/retry.py; supervisor reuses it)
+**Requirements**: REL-01, REL-02, REL-03, REL-05, REL-06, SRV-02
+**Success Criteria** (what must be TRUE):
+
+ 1. An unhandled exception in one plugin's `run_plugin` coroutine is absorbed by the supervisor before the `asyncio.TaskGroup` boundary; all other plugin coroutines keep running (verified by a test that crashes one plugin and asserts others continue)
+ 2. A plugin that exceeds N failures within a time window is parked (no further restart attempts) and the operator receives a notification through the existing dispatcher
+ 3. On a dead or disconnected Chrome process, the supervisor calls `plugin.relaunch()` which executes the full sequence: teardown, assign_proxy, setup (new browser + stealth + proxy auth), restore_session (no-op stub until Phase 23), login; `apply_stealth` is verified called on the relaunched browser
+ 4. Transient `sqlite3.OperationalError` (locked DB) on any `run_in_executor` read path in `run_plugin` is caught and isolated: the poll cycle is skipped, not terminated
+ 5. Each item's check/buy cycle runs under `asyncio.timeout(item_timeout_secs)`; the `write_queue.put()` calls are placed OUTSIDE the timeout context so a timed-out item cannot orphan a pending DB write
+ 6. SIGTERM and SIGINT trigger cooperative teardown (write-queue flush + browser teardown) via a platform-appropriate signal bridge (`sys.platform` branch handles Windows `NotImplementedError` on `loop.add_signal_handler`)
+
+**Plans**: 4 plans
+
+Plans:
+
+**Wave 1** *(parallel-safe; disjoint file)*
+
+- [x] 22-01-PLAN.md — core/plugin_base.py: concrete relaunch() (teardown→setup→restore_session→login; stealth re-injected via setup) + restore_session() no-op stub returns False; PLUGIN_API_VERSION stays 2 (REL-03)
+
+**Wave 2** *(orchestrator.py edits serialize — same-file, no parallel)*
+
+- [x] 22-02-PLAN.md — run_plugin hardening: cfg param + sqlite3.OperationalError read isolation on items read + per-item asyncio.timeout(item_timeout_secs); write_queue.put stays outside (REL-05, REL-06)
+
+**Wave 3** *(blocked on 22-01 + 22-02)*
+
+- [x] 22-03-PLAN.md — supervise() wrapper + _is_browser_dead_exc + failure budget (alert_on_errors / 600s deque) + park notify + browser-dead assign_proxy→relaunch + async_main create_task wiring (REL-01, REL-02)
+
+**Wave 4** *(blocked on 22-03)*
+
+- [x] 22-04-PLAN.md — _register_signals (POSIX add_signal_handler / Windows signal.signal fallback) + _flush_write_queue manual drain + async_main signal registration + pre-teardown flush (SRV-02)
+
+**Research flag** (RESOLVED via 22-RESEARCH.md): the nodriver stealth-persistence question is answered — `add_script_to_evaluate_on_new_document` is a per-session CDP command that is NOT persisted across `Browser.stop()` + `Browser.create()`; every plugin’s setup() re-injects stealth via apply_stealth, so relaunch() calling setup() is sufficient (HIGH confidence, confirmed against installed nodriver 0.50.3 source).
+
+### Phase 23: Encrypted Session Persistence
+
+**Goal**: Users can opt into encrypted cookie persistence so the bot skips re-login and MFA across restarts without storing any auth material in plaintext
+**Depends on**: Phase 22 (supervisor calls `restore_session` on relaunch; Phase 22 ships a no-op stub that this phase replaces)
+**Requirements**: REL-04
+**Success Criteria** (what must be TRUE):
+
+ 1. When `platforms..session_persistence: true`, cookies are saved after successful login via `core/session_store.py` using Fernet encryption (same scrypt KDF as `EncryptedFileBackend`); no plaintext `.json` or `.pickle` file is written to disk
+ 2. On bot restart or plugin relaunch, cookies are restored via raw CDP `cdp.storage.set_cookies()` (bypassing the confirmed `CookieJar.set_all()` bug); the restore path returns `True` if a session file existed, `False` otherwise (no crash if file is absent)
+ 3. The `data/sessions/` directory is in `.gitignore`; a CI check confirms no session file pattern matches are committed
+ 4. The Phase 22 no-op `restore_session` stub in `plugin.relaunch()` is replaced by the live `SessionStore.restore()` call
+
+**Plans**: TBD
+**Research flag**: NEEDS PLAN-PHASE RESEARCH — `nodriver cdp.storage.set_cookies()` exact import path and `CookieParam` constructor signature should be verified against the installed `nodriver==0.50.3` package before implementing the restore path. The workaround is confirmed from issues #1816/#2020 but the exact API shape needs local verification.
+
+### Phase 24: Health Surface + Server Safety
+
+**Goal**: Operators can query per-plugin liveness and error state from the CLI or web UI, receive alerts when a plugin degrades, and run the bot headlessly on a server without a pygame import crash
+**Depends on**: Phases 22 and 23 (health surface reads from supervisor + session state; SRV-01 is self-contained but grouped here as a low-dependency finish item)
+**Requirements**: REL-07, SRV-01
+**Success Criteria** (what must be TRUE):
+
+ 1. `BotService.get_status()` returns a structured dict including `{"running": bool, "uptime_secs": float, "plugins": {name: {"status": str, "last_heartbeat": float, "consecutive_errors": int, "items_checked": int, "orders_confirmed": int}}}` queryable from CLI (`shoppybot status`) and the FastAPI `/status` endpoint
+ 2. When a plugin's `consecutive_errors` exceeds the configured `alert_on_errors` threshold, a `health_degraded` event is dispatched through the existing notification fan-out channels
+ 3. On a headless host with no audio device, the sound notifier degrades to a silent no-op at import time rather than crashing; the bot starts and runs normally without pygame
+
+**Plans**: 5 plans
+
+Plans:
+
+**Wave 1** *(parallel-safe; disjoint files)*
+
+- [x] 24-01-PLAN.md — core/health.py HealthRegistry (per-plugin status/heartbeat/consecutive_errors/items_checked/orders_confirmed) + JSON-safe snapshot + in-memory armed/disarmed dedup + tests (REL-07)
+- [x] 24-02-PLAN.md — utils.py pygame headless guard: _initialize_audio()/_AUDIO_AVAILABLE, play_sound no-op when no device, redundant mixer.init removed + tests (SRV-01)
+
+**Wave 2** *(blocked on 24-01; disjoint files service/orchestrator/cli)*
+
+- [x] 24-03-PLAN.md — core/service.py get_status() locked shape {running,uptime_secs,plugins{...}} + _start_time uptime + single HealthRegistry ref wired into async_main; /status endpoint unchanged + JSON-serializable test (REL-07)
+- [x] 24-04-PLAN.md — core/orchestrator.py wiring: health=None kwarg through async_main/supervise/run_plugin; heartbeat/items_checked/status transitions/orders_confirmed; health_degraded fire-once+re-arm dedup via existing dispatcher, distinct from plugin_parked + tests (REL-07)
+- [x] 24-05-PLAN.md — core/cli/status.py shoppybot status subcommand (per-plugin table + --json, no network, in-process-state note) + subparser registration + tests (REL-07)
+
+**UI hint**: yes
+
+---
+
+## Progress
+
+| Phase | Milestone | Plans | Status | Completed |
+|-------|-----------|-------|--------|-----------|
+| 1. Foundations + Security | v1 | 5/5 | Complete | 2026-06-02 |
+| 2. Plugin Migration | v1 | 6/6 | Complete | 2026-06-03 |
+| 3. Community Documentation | v1 | 2/2 | Complete | 2026-06-03 |
+| 4. Async Orchestrator | v1 | 5/5 | Complete | 2026-06-03 |
+| 5. Notification System | v1 | 5/5 | Complete | 2026-06-03 |
+| 6. Platform Expansion | v1 | 5/5 | Complete | 2026-06-03 |
+| 7. Modular Core Service | v2.0 | 3/3 | Complete | 2026-06-04 |
+| 8. Credential Store | v2.0 | 4/4 | Complete | 2026-06-04 |
+| 9. CLI Front-End | v2.0 | 4/4 | Complete | 2026-06-04 |
+| 10. Optional Web UI | v2.0 | 4/4 | Complete | 2026-06-04 |
+| 11. Cross-Platform Verification | v2.0 | 5/5 | Complete | 2026-06-05 |
+| 12. Stability Foundation | v3.0 | 4/4 | Complete | 2026-06-09 |
+| 13. Anti-Detection Layer 1 — Fingerprint + Proxy | v3.0 | 3/3 | Complete | 2026-06-09 |
+| 14. Anti-Detection Layer 2 — CAPTCHA Solving | v3.0 | 3/3 | Complete | 2026-06-09 |
+| 15. Plugin Ecosystem Registry | v3.0 | 3/3 | Complete | 2026-06-09 |
+| 16. Price Monitoring | v3.0 | 4/4 | Complete | 2026-06-10 |
+| 17. Test Hardening | v3.0 | 4/4 | Complete | 2026-06-10 |
+| 18. Safety Gate + Config Foundation | v4.0 | 4/4 | Complete | 2026-06-11 |
+| 19. DB Schema + Confirmation Detection | v4.0 | 4/4 | Complete | 2026-06-11 |
+| 20. Checkout Profile + Form-Fill | v4.0 | 4/4 | Complete | 2026-06-11 |
+| 21. Per-Step Timeouts + Unified Retry + Cart-Retry | v4.0 | 4/4 | Complete | 2026-06-12 |
+| 22. Supervisor + Browser Relaunch + Server Safety | v4.0 | 4/4 | Complete | 2026-06-12 |
+| 23. Encrypted Session Persistence | v4.0 | 4/4 | Complete | 2026-06-12 |
+| 24. Health Surface + Server Safety | v4.0 | 5/5 | Complete | 2026-06-12 |
+
+All 66 v1+v2.0 requirements satisfied. v3.0: 18 requirements mapped across Phases 12-17. v4.0: 17 requirements mapped across Phases 18-24.
+
+---
+
+*Last updated: 2026-06-12 — Phase 24 planned (5 plans, 2 waves)*
diff --git a/.planning/phases/18-safety-gate-config-foundation/18-01-PLAN.md b/.planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-01-PLAN.md
similarity index 100%
rename from .planning/phases/18-safety-gate-config-foundation/18-01-PLAN.md
rename to .planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-01-PLAN.md
diff --git a/.planning/phases/18-safety-gate-config-foundation/18-01-SUMMARY.md b/.planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-01-SUMMARY.md
similarity index 100%
rename from .planning/phases/18-safety-gate-config-foundation/18-01-SUMMARY.md
rename to .planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-01-SUMMARY.md
diff --git a/.planning/phases/18-safety-gate-config-foundation/18-02-PLAN.md b/.planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-02-PLAN.md
similarity index 100%
rename from .planning/phases/18-safety-gate-config-foundation/18-02-PLAN.md
rename to .planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-02-PLAN.md
diff --git a/.planning/phases/18-safety-gate-config-foundation/18-02-SUMMARY.md b/.planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-02-SUMMARY.md
similarity index 100%
rename from .planning/phases/18-safety-gate-config-foundation/18-02-SUMMARY.md
rename to .planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-02-SUMMARY.md
diff --git a/.planning/phases/18-safety-gate-config-foundation/18-03-PLAN.md b/.planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-03-PLAN.md
similarity index 100%
rename from .planning/phases/18-safety-gate-config-foundation/18-03-PLAN.md
rename to .planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-03-PLAN.md
diff --git a/.planning/phases/18-safety-gate-config-foundation/18-03-SUMMARY.md b/.planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-03-SUMMARY.md
similarity index 100%
rename from .planning/phases/18-safety-gate-config-foundation/18-03-SUMMARY.md
rename to .planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-03-SUMMARY.md
diff --git a/.planning/phases/18-safety-gate-config-foundation/18-04-PLAN.md b/.planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-04-PLAN.md
similarity index 100%
rename from .planning/phases/18-safety-gate-config-foundation/18-04-PLAN.md
rename to .planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-04-PLAN.md
diff --git a/.planning/phases/18-safety-gate-config-foundation/18-04-SUMMARY.md b/.planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-04-SUMMARY.md
similarity index 100%
rename from .planning/phases/18-safety-gate-config-foundation/18-04-SUMMARY.md
rename to .planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-04-SUMMARY.md
diff --git a/.planning/phases/18-safety-gate-config-foundation/18-CONTEXT.md b/.planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-CONTEXT.md
similarity index 100%
rename from .planning/phases/18-safety-gate-config-foundation/18-CONTEXT.md
rename to .planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-CONTEXT.md
diff --git a/.planning/phases/18-safety-gate-config-foundation/18-HUMAN-UAT.md b/.planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-HUMAN-UAT.md
similarity index 100%
rename from .planning/phases/18-safety-gate-config-foundation/18-HUMAN-UAT.md
rename to .planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-HUMAN-UAT.md
diff --git a/.planning/phases/18-safety-gate-config-foundation/18-PATTERNS.md b/.planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-PATTERNS.md
similarity index 100%
rename from .planning/phases/18-safety-gate-config-foundation/18-PATTERNS.md
rename to .planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-PATTERNS.md
diff --git a/.planning/phases/18-safety-gate-config-foundation/18-RESEARCH.md b/.planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-RESEARCH.md
similarity index 100%
rename from .planning/phases/18-safety-gate-config-foundation/18-RESEARCH.md
rename to .planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-RESEARCH.md
diff --git a/.planning/phases/18-safety-gate-config-foundation/18-REVIEW.md b/.planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-REVIEW.md
similarity index 100%
rename from .planning/phases/18-safety-gate-config-foundation/18-REVIEW.md
rename to .planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-REVIEW.md
diff --git a/.planning/phases/18-safety-gate-config-foundation/18-VALIDATION.md b/.planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-VALIDATION.md
similarity index 100%
rename from .planning/phases/18-safety-gate-config-foundation/18-VALIDATION.md
rename to .planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-VALIDATION.md
diff --git a/.planning/phases/18-safety-gate-config-foundation/18-VERIFICATION.md b/.planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-VERIFICATION.md
similarity index 100%
rename from .planning/phases/18-safety-gate-config-foundation/18-VERIFICATION.md
rename to .planning/milestones/v4.0-phases/18-safety-gate-config-foundation/18-VERIFICATION.md
diff --git a/.planning/phases/19-db-schema-confirmation-detection/19-01-PLAN.md b/.planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-01-PLAN.md
similarity index 100%
rename from .planning/phases/19-db-schema-confirmation-detection/19-01-PLAN.md
rename to .planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-01-PLAN.md
diff --git a/.planning/phases/19-db-schema-confirmation-detection/19-01-SUMMARY.md b/.planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-01-SUMMARY.md
similarity index 100%
rename from .planning/phases/19-db-schema-confirmation-detection/19-01-SUMMARY.md
rename to .planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-01-SUMMARY.md
diff --git a/.planning/phases/19-db-schema-confirmation-detection/19-02-PLAN.md b/.planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-02-PLAN.md
similarity index 100%
rename from .planning/phases/19-db-schema-confirmation-detection/19-02-PLAN.md
rename to .planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-02-PLAN.md
diff --git a/.planning/phases/19-db-schema-confirmation-detection/19-02-SUMMARY.md b/.planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-02-SUMMARY.md
similarity index 100%
rename from .planning/phases/19-db-schema-confirmation-detection/19-02-SUMMARY.md
rename to .planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-02-SUMMARY.md
diff --git a/.planning/phases/19-db-schema-confirmation-detection/19-03-PLAN.md b/.planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-03-PLAN.md
similarity index 100%
rename from .planning/phases/19-db-schema-confirmation-detection/19-03-PLAN.md
rename to .planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-03-PLAN.md
diff --git a/.planning/phases/19-db-schema-confirmation-detection/19-03-SUMMARY.md b/.planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-03-SUMMARY.md
similarity index 100%
rename from .planning/phases/19-db-schema-confirmation-detection/19-03-SUMMARY.md
rename to .planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-03-SUMMARY.md
diff --git a/.planning/phases/19-db-schema-confirmation-detection/19-04-PLAN.md b/.planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-04-PLAN.md
similarity index 100%
rename from .planning/phases/19-db-schema-confirmation-detection/19-04-PLAN.md
rename to .planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-04-PLAN.md
diff --git a/.planning/phases/19-db-schema-confirmation-detection/19-04-SUMMARY.md b/.planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-04-SUMMARY.md
similarity index 100%
rename from .planning/phases/19-db-schema-confirmation-detection/19-04-SUMMARY.md
rename to .planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-04-SUMMARY.md
diff --git a/.planning/phases/19-db-schema-confirmation-detection/19-CONTEXT.md b/.planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-CONTEXT.md
similarity index 100%
rename from .planning/phases/19-db-schema-confirmation-detection/19-CONTEXT.md
rename to .planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-CONTEXT.md
diff --git a/.planning/phases/19-db-schema-confirmation-detection/19-HUMAN-UAT.md b/.planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-HUMAN-UAT.md
similarity index 100%
rename from .planning/phases/19-db-schema-confirmation-detection/19-HUMAN-UAT.md
rename to .planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-HUMAN-UAT.md
diff --git a/.planning/phases/19-db-schema-confirmation-detection/19-PATTERNS.md b/.planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-PATTERNS.md
similarity index 100%
rename from .planning/phases/19-db-schema-confirmation-detection/19-PATTERNS.md
rename to .planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-PATTERNS.md
diff --git a/.planning/phases/19-db-schema-confirmation-detection/19-RESEARCH.md b/.planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-RESEARCH.md
similarity index 100%
rename from .planning/phases/19-db-schema-confirmation-detection/19-RESEARCH.md
rename to .planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-RESEARCH.md
diff --git a/.planning/phases/19-db-schema-confirmation-detection/19-REVIEW.md b/.planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-REVIEW.md
similarity index 100%
rename from .planning/phases/19-db-schema-confirmation-detection/19-REVIEW.md
rename to .planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-REVIEW.md
diff --git a/.planning/phases/19-db-schema-confirmation-detection/19-VALIDATION.md b/.planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-VALIDATION.md
similarity index 100%
rename from .planning/phases/19-db-schema-confirmation-detection/19-VALIDATION.md
rename to .planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-VALIDATION.md
diff --git a/.planning/phases/19-db-schema-confirmation-detection/19-VERIFICATION.md b/.planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-VERIFICATION.md
similarity index 100%
rename from .planning/phases/19-db-schema-confirmation-detection/19-VERIFICATION.md
rename to .planning/milestones/v4.0-phases/19-db-schema-confirmation-detection/19-VERIFICATION.md
diff --git a/.planning/phases/20-checkout-profile-form-fill/20-01-PLAN.md b/.planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-01-PLAN.md
similarity index 100%
rename from .planning/phases/20-checkout-profile-form-fill/20-01-PLAN.md
rename to .planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-01-PLAN.md
diff --git a/.planning/phases/20-checkout-profile-form-fill/20-01-SUMMARY.md b/.planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-01-SUMMARY.md
similarity index 100%
rename from .planning/phases/20-checkout-profile-form-fill/20-01-SUMMARY.md
rename to .planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-01-SUMMARY.md
diff --git a/.planning/phases/20-checkout-profile-form-fill/20-02-PLAN.md b/.planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-02-PLAN.md
similarity index 100%
rename from .planning/phases/20-checkout-profile-form-fill/20-02-PLAN.md
rename to .planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-02-PLAN.md
diff --git a/.planning/phases/20-checkout-profile-form-fill/20-02-SUMMARY.md b/.planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-02-SUMMARY.md
similarity index 100%
rename from .planning/phases/20-checkout-profile-form-fill/20-02-SUMMARY.md
rename to .planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-02-SUMMARY.md
diff --git a/.planning/phases/20-checkout-profile-form-fill/20-03-PLAN.md b/.planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-03-PLAN.md
similarity index 100%
rename from .planning/phases/20-checkout-profile-form-fill/20-03-PLAN.md
rename to .planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-03-PLAN.md
diff --git a/.planning/phases/20-checkout-profile-form-fill/20-03-SUMMARY.md b/.planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-03-SUMMARY.md
similarity index 100%
rename from .planning/phases/20-checkout-profile-form-fill/20-03-SUMMARY.md
rename to .planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-03-SUMMARY.md
diff --git a/.planning/phases/20-checkout-profile-form-fill/20-04-PLAN.md b/.planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-04-PLAN.md
similarity index 100%
rename from .planning/phases/20-checkout-profile-form-fill/20-04-PLAN.md
rename to .planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-04-PLAN.md
diff --git a/.planning/phases/20-checkout-profile-form-fill/20-04-SUMMARY.md b/.planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-04-SUMMARY.md
similarity index 100%
rename from .planning/phases/20-checkout-profile-form-fill/20-04-SUMMARY.md
rename to .planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-04-SUMMARY.md
diff --git a/.planning/phases/20-checkout-profile-form-fill/20-CONTEXT.md b/.planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-CONTEXT.md
similarity index 100%
rename from .planning/phases/20-checkout-profile-form-fill/20-CONTEXT.md
rename to .planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-CONTEXT.md
diff --git a/.planning/phases/20-checkout-profile-form-fill/20-HUMAN-UAT.md b/.planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-HUMAN-UAT.md
similarity index 100%
rename from .planning/phases/20-checkout-profile-form-fill/20-HUMAN-UAT.md
rename to .planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-HUMAN-UAT.md
diff --git a/.planning/phases/20-checkout-profile-form-fill/20-PATTERNS.md b/.planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-PATTERNS.md
similarity index 100%
rename from .planning/phases/20-checkout-profile-form-fill/20-PATTERNS.md
rename to .planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-PATTERNS.md
diff --git a/.planning/phases/20-checkout-profile-form-fill/20-RESEARCH.md b/.planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-RESEARCH.md
similarity index 100%
rename from .planning/phases/20-checkout-profile-form-fill/20-RESEARCH.md
rename to .planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-RESEARCH.md
diff --git a/.planning/phases/20-checkout-profile-form-fill/20-REVIEW.md b/.planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-REVIEW.md
similarity index 100%
rename from .planning/phases/20-checkout-profile-form-fill/20-REVIEW.md
rename to .planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-REVIEW.md
diff --git a/.planning/phases/20-checkout-profile-form-fill/20-VALIDATION.md b/.planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-VALIDATION.md
similarity index 100%
rename from .planning/phases/20-checkout-profile-form-fill/20-VALIDATION.md
rename to .planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-VALIDATION.md
diff --git a/.planning/phases/20-checkout-profile-form-fill/20-VERIFICATION.md b/.planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-VERIFICATION.md
similarity index 100%
rename from .planning/phases/20-checkout-profile-form-fill/20-VERIFICATION.md
rename to .planning/milestones/v4.0-phases/20-checkout-profile-form-fill/20-VERIFICATION.md
diff --git a/.planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-01-PLAN.md b/.planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-01-PLAN.md
similarity index 100%
rename from .planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-01-PLAN.md
rename to .planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-01-PLAN.md
diff --git a/.planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-01-SUMMARY.md b/.planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-01-SUMMARY.md
similarity index 100%
rename from .planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-01-SUMMARY.md
rename to .planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-01-SUMMARY.md
diff --git a/.planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-02-PLAN.md b/.planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-02-PLAN.md
similarity index 100%
rename from .planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-02-PLAN.md
rename to .planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-02-PLAN.md
diff --git a/.planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-02-SUMMARY.md b/.planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-02-SUMMARY.md
similarity index 100%
rename from .planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-02-SUMMARY.md
rename to .planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-02-SUMMARY.md
diff --git a/.planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-03-PLAN.md b/.planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-03-PLAN.md
similarity index 100%
rename from .planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-03-PLAN.md
rename to .planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-03-PLAN.md
diff --git a/.planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-03-SUMMARY.md b/.planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-03-SUMMARY.md
similarity index 100%
rename from .planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-03-SUMMARY.md
rename to .planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-03-SUMMARY.md
diff --git a/.planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-04-PLAN.md b/.planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-04-PLAN.md
similarity index 100%
rename from .planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-04-PLAN.md
rename to .planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-04-PLAN.md
diff --git a/.planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-04-SUMMARY.md b/.planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-04-SUMMARY.md
similarity index 100%
rename from .planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-04-SUMMARY.md
rename to .planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-04-SUMMARY.md
diff --git a/.planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-CONTEXT.md b/.planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-CONTEXT.md
similarity index 100%
rename from .planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-CONTEXT.md
rename to .planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-CONTEXT.md
diff --git a/.planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-HUMAN-UAT.md b/.planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-HUMAN-UAT.md
similarity index 100%
rename from .planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-HUMAN-UAT.md
rename to .planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-HUMAN-UAT.md
diff --git a/.planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-PATTERNS.md b/.planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-PATTERNS.md
similarity index 100%
rename from .planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-PATTERNS.md
rename to .planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-PATTERNS.md
diff --git a/.planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-RESEARCH.md b/.planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-RESEARCH.md
similarity index 100%
rename from .planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-RESEARCH.md
rename to .planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-RESEARCH.md
diff --git a/.planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-REVIEW.md b/.planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-REVIEW.md
similarity index 100%
rename from .planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-REVIEW.md
rename to .planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-REVIEW.md
diff --git a/.planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-VALIDATION.md b/.planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-VALIDATION.md
similarity index 100%
rename from .planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-VALIDATION.md
rename to .planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-VALIDATION.md
diff --git a/.planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-VERIFICATION.md b/.planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-VERIFICATION.md
similarity index 100%
rename from .planning/phases/21-per-step-timeouts-unified-retry-cart-retry/21-VERIFICATION.md
rename to .planning/milestones/v4.0-phases/21-per-step-timeouts-unified-retry-cart-retry/21-VERIFICATION.md
diff --git a/.planning/phases/22-supervisor-browser-relaunch-server-safety/22-01-PLAN.md b/.planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-01-PLAN.md
similarity index 100%
rename from .planning/phases/22-supervisor-browser-relaunch-server-safety/22-01-PLAN.md
rename to .planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-01-PLAN.md
diff --git a/.planning/phases/22-supervisor-browser-relaunch-server-safety/22-01-SUMMARY.md b/.planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-01-SUMMARY.md
similarity index 100%
rename from .planning/phases/22-supervisor-browser-relaunch-server-safety/22-01-SUMMARY.md
rename to .planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-01-SUMMARY.md
diff --git a/.planning/phases/22-supervisor-browser-relaunch-server-safety/22-02-PLAN.md b/.planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-02-PLAN.md
similarity index 100%
rename from .planning/phases/22-supervisor-browser-relaunch-server-safety/22-02-PLAN.md
rename to .planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-02-PLAN.md
diff --git a/.planning/phases/22-supervisor-browser-relaunch-server-safety/22-02-SUMMARY.md b/.planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-02-SUMMARY.md
similarity index 100%
rename from .planning/phases/22-supervisor-browser-relaunch-server-safety/22-02-SUMMARY.md
rename to .planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-02-SUMMARY.md
diff --git a/.planning/phases/22-supervisor-browser-relaunch-server-safety/22-03-PLAN.md b/.planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-03-PLAN.md
similarity index 100%
rename from .planning/phases/22-supervisor-browser-relaunch-server-safety/22-03-PLAN.md
rename to .planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-03-PLAN.md
diff --git a/.planning/phases/22-supervisor-browser-relaunch-server-safety/22-03-SUMMARY.md b/.planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-03-SUMMARY.md
similarity index 100%
rename from .planning/phases/22-supervisor-browser-relaunch-server-safety/22-03-SUMMARY.md
rename to .planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-03-SUMMARY.md
diff --git a/.planning/phases/22-supervisor-browser-relaunch-server-safety/22-04-PLAN.md b/.planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-04-PLAN.md
similarity index 100%
rename from .planning/phases/22-supervisor-browser-relaunch-server-safety/22-04-PLAN.md
rename to .planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-04-PLAN.md
diff --git a/.planning/phases/22-supervisor-browser-relaunch-server-safety/22-04-SUMMARY.md b/.planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-04-SUMMARY.md
similarity index 100%
rename from .planning/phases/22-supervisor-browser-relaunch-server-safety/22-04-SUMMARY.md
rename to .planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-04-SUMMARY.md
diff --git a/.planning/phases/22-supervisor-browser-relaunch-server-safety/22-CONTEXT.md b/.planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-CONTEXT.md
similarity index 100%
rename from .planning/phases/22-supervisor-browser-relaunch-server-safety/22-CONTEXT.md
rename to .planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-CONTEXT.md
diff --git a/.planning/phases/22-supervisor-browser-relaunch-server-safety/22-HUMAN-UAT.md b/.planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-HUMAN-UAT.md
similarity index 100%
rename from .planning/phases/22-supervisor-browser-relaunch-server-safety/22-HUMAN-UAT.md
rename to .planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-HUMAN-UAT.md
diff --git a/.planning/phases/22-supervisor-browser-relaunch-server-safety/22-PATTERNS.md b/.planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-PATTERNS.md
similarity index 100%
rename from .planning/phases/22-supervisor-browser-relaunch-server-safety/22-PATTERNS.md
rename to .planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-PATTERNS.md
diff --git a/.planning/phases/22-supervisor-browser-relaunch-server-safety/22-RESEARCH.md b/.planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-RESEARCH.md
similarity index 100%
rename from .planning/phases/22-supervisor-browser-relaunch-server-safety/22-RESEARCH.md
rename to .planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-RESEARCH.md
diff --git a/.planning/phases/22-supervisor-browser-relaunch-server-safety/22-REVIEW.md b/.planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-REVIEW.md
similarity index 100%
rename from .planning/phases/22-supervisor-browser-relaunch-server-safety/22-REVIEW.md
rename to .planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-REVIEW.md
diff --git a/.planning/phases/22-supervisor-browser-relaunch-server-safety/22-VALIDATION.md b/.planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-VALIDATION.md
similarity index 100%
rename from .planning/phases/22-supervisor-browser-relaunch-server-safety/22-VALIDATION.md
rename to .planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-VALIDATION.md
diff --git a/.planning/phases/22-supervisor-browser-relaunch-server-safety/22-VERIFICATION.md b/.planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-VERIFICATION.md
similarity index 100%
rename from .planning/phases/22-supervisor-browser-relaunch-server-safety/22-VERIFICATION.md
rename to .planning/milestones/v4.0-phases/22-supervisor-browser-relaunch-server-safety/22-VERIFICATION.md
diff --git a/.planning/phases/23-encrypted-session-persistence/23-01-PLAN.md b/.planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-01-PLAN.md
similarity index 100%
rename from .planning/phases/23-encrypted-session-persistence/23-01-PLAN.md
rename to .planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-01-PLAN.md
diff --git a/.planning/phases/23-encrypted-session-persistence/23-01-SUMMARY.md b/.planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-01-SUMMARY.md
similarity index 100%
rename from .planning/phases/23-encrypted-session-persistence/23-01-SUMMARY.md
rename to .planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-01-SUMMARY.md
diff --git a/.planning/phases/23-encrypted-session-persistence/23-02-PLAN.md b/.planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-02-PLAN.md
similarity index 100%
rename from .planning/phases/23-encrypted-session-persistence/23-02-PLAN.md
rename to .planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-02-PLAN.md
diff --git a/.planning/phases/23-encrypted-session-persistence/23-02-SUMMARY.md b/.planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-02-SUMMARY.md
similarity index 100%
rename from .planning/phases/23-encrypted-session-persistence/23-02-SUMMARY.md
rename to .planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-02-SUMMARY.md
diff --git a/.planning/phases/23-encrypted-session-persistence/23-03-PLAN.md b/.planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-03-PLAN.md
similarity index 100%
rename from .planning/phases/23-encrypted-session-persistence/23-03-PLAN.md
rename to .planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-03-PLAN.md
diff --git a/.planning/phases/23-encrypted-session-persistence/23-03-SUMMARY.md b/.planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-03-SUMMARY.md
similarity index 100%
rename from .planning/phases/23-encrypted-session-persistence/23-03-SUMMARY.md
rename to .planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-03-SUMMARY.md
diff --git a/.planning/phases/23-encrypted-session-persistence/23-04-PLAN.md b/.planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-04-PLAN.md
similarity index 100%
rename from .planning/phases/23-encrypted-session-persistence/23-04-PLAN.md
rename to .planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-04-PLAN.md
diff --git a/.planning/phases/23-encrypted-session-persistence/23-04-SUMMARY.md b/.planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-04-SUMMARY.md
similarity index 100%
rename from .planning/phases/23-encrypted-session-persistence/23-04-SUMMARY.md
rename to .planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-04-SUMMARY.md
diff --git a/.planning/phases/23-encrypted-session-persistence/23-CONTEXT.md b/.planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-CONTEXT.md
similarity index 100%
rename from .planning/phases/23-encrypted-session-persistence/23-CONTEXT.md
rename to .planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-CONTEXT.md
diff --git a/.planning/phases/23-encrypted-session-persistence/23-HUMAN-UAT.md b/.planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-HUMAN-UAT.md
similarity index 100%
rename from .planning/phases/23-encrypted-session-persistence/23-HUMAN-UAT.md
rename to .planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-HUMAN-UAT.md
diff --git a/.planning/phases/23-encrypted-session-persistence/23-PATTERNS.md b/.planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-PATTERNS.md
similarity index 100%
rename from .planning/phases/23-encrypted-session-persistence/23-PATTERNS.md
rename to .planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-PATTERNS.md
diff --git a/.planning/phases/23-encrypted-session-persistence/23-RESEARCH.md b/.planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-RESEARCH.md
similarity index 100%
rename from .planning/phases/23-encrypted-session-persistence/23-RESEARCH.md
rename to .planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-RESEARCH.md
diff --git a/.planning/phases/23-encrypted-session-persistence/23-REVIEW-FIX.md b/.planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-REVIEW-FIX.md
similarity index 100%
rename from .planning/phases/23-encrypted-session-persistence/23-REVIEW-FIX.md
rename to .planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-REVIEW-FIX.md
diff --git a/.planning/phases/23-encrypted-session-persistence/23-REVIEW.md b/.planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-REVIEW.md
similarity index 100%
rename from .planning/phases/23-encrypted-session-persistence/23-REVIEW.md
rename to .planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-REVIEW.md
diff --git a/.planning/phases/23-encrypted-session-persistence/23-VALIDATION.md b/.planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-VALIDATION.md
similarity index 100%
rename from .planning/phases/23-encrypted-session-persistence/23-VALIDATION.md
rename to .planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-VALIDATION.md
diff --git a/.planning/phases/23-encrypted-session-persistence/23-VERIFICATION.md b/.planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-VERIFICATION.md
similarity index 100%
rename from .planning/phases/23-encrypted-session-persistence/23-VERIFICATION.md
rename to .planning/milestones/v4.0-phases/23-encrypted-session-persistence/23-VERIFICATION.md
diff --git a/.planning/phases/24-health-surface-server-safety/24-01-PLAN.md b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-01-PLAN.md
similarity index 100%
rename from .planning/phases/24-health-surface-server-safety/24-01-PLAN.md
rename to .planning/milestones/v4.0-phases/24-health-surface-server-safety/24-01-PLAN.md
diff --git a/.planning/phases/24-health-surface-server-safety/24-01-SUMMARY.md b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-01-SUMMARY.md
similarity index 100%
rename from .planning/phases/24-health-surface-server-safety/24-01-SUMMARY.md
rename to .planning/milestones/v4.0-phases/24-health-surface-server-safety/24-01-SUMMARY.md
diff --git a/.planning/phases/24-health-surface-server-safety/24-02-PLAN.md b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-02-PLAN.md
similarity index 100%
rename from .planning/phases/24-health-surface-server-safety/24-02-PLAN.md
rename to .planning/milestones/v4.0-phases/24-health-surface-server-safety/24-02-PLAN.md
diff --git a/.planning/phases/24-health-surface-server-safety/24-02-SUMMARY.md b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-02-SUMMARY.md
similarity index 100%
rename from .planning/phases/24-health-surface-server-safety/24-02-SUMMARY.md
rename to .planning/milestones/v4.0-phases/24-health-surface-server-safety/24-02-SUMMARY.md
diff --git a/.planning/phases/24-health-surface-server-safety/24-03-PLAN.md b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-03-PLAN.md
similarity index 100%
rename from .planning/phases/24-health-surface-server-safety/24-03-PLAN.md
rename to .planning/milestones/v4.0-phases/24-health-surface-server-safety/24-03-PLAN.md
diff --git a/.planning/phases/24-health-surface-server-safety/24-03-SUMMARY.md b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-03-SUMMARY.md
similarity index 100%
rename from .planning/phases/24-health-surface-server-safety/24-03-SUMMARY.md
rename to .planning/milestones/v4.0-phases/24-health-surface-server-safety/24-03-SUMMARY.md
diff --git a/.planning/phases/24-health-surface-server-safety/24-04-PLAN.md b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-04-PLAN.md
similarity index 100%
rename from .planning/phases/24-health-surface-server-safety/24-04-PLAN.md
rename to .planning/milestones/v4.0-phases/24-health-surface-server-safety/24-04-PLAN.md
diff --git a/.planning/phases/24-health-surface-server-safety/24-04-SUMMARY.md b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-04-SUMMARY.md
similarity index 100%
rename from .planning/phases/24-health-surface-server-safety/24-04-SUMMARY.md
rename to .planning/milestones/v4.0-phases/24-health-surface-server-safety/24-04-SUMMARY.md
diff --git a/.planning/phases/24-health-surface-server-safety/24-05-PLAN.md b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-05-PLAN.md
similarity index 100%
rename from .planning/phases/24-health-surface-server-safety/24-05-PLAN.md
rename to .planning/milestones/v4.0-phases/24-health-surface-server-safety/24-05-PLAN.md
diff --git a/.planning/phases/24-health-surface-server-safety/24-05-SUMMARY.md b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-05-SUMMARY.md
similarity index 100%
rename from .planning/phases/24-health-surface-server-safety/24-05-SUMMARY.md
rename to .planning/milestones/v4.0-phases/24-health-surface-server-safety/24-05-SUMMARY.md
diff --git a/.planning/phases/24-health-surface-server-safety/24-CONTEXT.md b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-CONTEXT.md
similarity index 100%
rename from .planning/phases/24-health-surface-server-safety/24-CONTEXT.md
rename to .planning/milestones/v4.0-phases/24-health-surface-server-safety/24-CONTEXT.md
diff --git a/.planning/phases/24-health-surface-server-safety/24-HUMAN-UAT.md b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-HUMAN-UAT.md
similarity index 100%
rename from .planning/phases/24-health-surface-server-safety/24-HUMAN-UAT.md
rename to .planning/milestones/v4.0-phases/24-health-surface-server-safety/24-HUMAN-UAT.md
diff --git a/.planning/phases/24-health-surface-server-safety/24-PATTERNS.md b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-PATTERNS.md
similarity index 100%
rename from .planning/phases/24-health-surface-server-safety/24-PATTERNS.md
rename to .planning/milestones/v4.0-phases/24-health-surface-server-safety/24-PATTERNS.md
diff --git a/.planning/phases/24-health-surface-server-safety/24-RESEARCH.md b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-RESEARCH.md
similarity index 100%
rename from .planning/phases/24-health-surface-server-safety/24-RESEARCH.md
rename to .planning/milestones/v4.0-phases/24-health-surface-server-safety/24-RESEARCH.md
diff --git a/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-REVIEW-FIX.md b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-REVIEW-FIX.md
new file mode 100644
index 0000000..b4933e2
--- /dev/null
+++ b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-REVIEW-FIX.md
@@ -0,0 +1,70 @@
+---
+phase: 24-health-surface-server-safety
+fixed_at: 2026-06-12T00:00:00Z
+review_path: .planning/phases/24-health-surface-server-safety/24-REVIEW.md
+iteration: 1
+findings_in_scope: 5
+fixed: 5
+skipped: 0
+status: all_fixed
+---
+
+# Phase 24: Code Review Fix Report
+
+**Fixed at:** 2026-06-12
+**Source review:** .planning/phases/24-health-surface-server-safety/24-REVIEW.md
+**Iteration:** 1
+
+**Summary:**
+- Findings in scope: 5 (CR-01, WR-01, WR-02, WR-03, IN-02; IN-01 subsumed by CR-01 rewrite)
+- Fixed: 5
+- Skipped: 0
+
+## Fixed Issues
+
+### CR-01 + WR-03: Guard pygame import; wrap play_sound audio errors
+
+**Files modified:** `utils.py`, `tests/test_utils_audio.py`
+**Commit:** 8284735
+**Applied fix:**
+- Replaced bare `import pygame` with `try/except (ModuleNotFoundError, ImportError)` block; sets `_PYGAME_AVAILABLE` and `_pygame` alias.
+- `_initialize_audio()` short-circuits to False when `_PYGAME_AVAILABLE` is False (before calling mixer.init).
+- All `pygame.*` references in the module now use `_pygame.*`.
+- `play_sound` wraps `mixer.music.load/play` in `try/except _pygame.error` to swallow runtime audio errors (WR-03).
+- Dead `_AUDIO_AVAILABLE: bool = False` pre-declaration (IN-01) eliminated as part of the rewrite.
+- Updated `tests/test_utils_audio.py`: patches retargeted to `utils._pygame`, added non-vacuous import-failure simulation test (blocks pygame via `sys.modules["pygame"] = None`, reloads utils, asserts `_AUDIO_AVAILABLE=False` and `play_sound` no-ops), added WR-03 load/play error tests.
+
+**Suite result:** 755 passed, 2 skipped, 0 failures (baseline 752 passed; 3 new tests added)
+
+### WR-01: Add get_consecutive_errors reader; use in supervise() crash path
+
+**Files modified:** `core/health.py`, `core/orchestrator.py`
+**Commit:** d11bf2c
+**Applied fix:**
+- Added `HealthRegistry.get_consecutive_errors(name: str) -> int` that reads the live counter directly via `_ensure` + dict lookup.
+- Replaced `snap = health.get_snapshot(); consecutive = snap.get(plugin_name, {}).get("consecutive_errors", 0)` in `supervise()` with `consecutive = health.get_consecutive_errors(plugin_name)`. Removes the full deep-copy allocation on the hot crash path.
+
+### WR-02: Snapshot loop/task before guard in stop() to close TOCTOU
+
+**Files modified:** `core/service.py`
+**Commit:** 79e5419
+**Applied fix:**
+- Moved `loop = self._loop` and `task = self._task` captures to the top of `stop()`, before the `if not self._running or loop is None` guard. Guard and `call_soon_threadsafe` now operate on the same consistent local references even if the daemon thread nulls the instance attributes concurrently.
+
+### IN-02: Display heartbeat age instead of raw monotonic seconds
+
+**Files modified:** `core/cli/status.py`
+**Commit:** 3007ba1
+**Applied fix:**
+- Added `import time` and `now = time.monotonic()` in `_format_status_table`.
+- Replaced `f"{rec.get('last_heartbeat', 0.0):.1f}"` with `f"{now - rec['last_heartbeat']:.1f}s ago"` (or `"never"` when heartbeat is 0.0).
+
+### IN-01: Dead _AUDIO_AVAILABLE pre-declaration
+
+Subsumed by CR-01 fix. The line 8 `_AUDIO_AVAILABLE: bool = False` pre-declaration was not present in the rewritten utils.py; no separate commit required.
+
+---
+
+_Fixed: 2026-06-12_
+_Fixer: Claude (gsd-code-fixer)_
+_Iteration: 1_
diff --git a/.planning/phases/24-health-surface-server-safety/24-REVIEW.md b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-REVIEW.md
similarity index 100%
rename from .planning/phases/24-health-surface-server-safety/24-REVIEW.md
rename to .planning/milestones/v4.0-phases/24-health-surface-server-safety/24-REVIEW.md
diff --git a/.planning/phases/24-health-surface-server-safety/24-VALIDATION.md b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-VALIDATION.md
similarity index 100%
rename from .planning/phases/24-health-surface-server-safety/24-VALIDATION.md
rename to .planning/milestones/v4.0-phases/24-health-surface-server-safety/24-VALIDATION.md
diff --git a/.planning/phases/24-health-surface-server-safety/24-VERIFICATION.md b/.planning/milestones/v4.0-phases/24-health-surface-server-safety/24-VERIFICATION.md
similarity index 100%
rename from .planning/phases/24-health-surface-server-safety/24-VERIFICATION.md
rename to .planning/milestones/v4.0-phases/24-health-surface-server-safety/24-VERIFICATION.md