diff --git a/.planning/phases/07.3-security-es256-algorithm-upgrade-inserted/07.3-CONTEXT.md b/.planning/phases/07.3-security-es256-algorithm-upgrade-inserted/07.3-CONTEXT.md
new file mode 100644
index 0000000..c684c3b
--- /dev/null
+++ b/.planning/phases/07.3-security-es256-algorithm-upgrade-inserted/07.3-CONTEXT.md
@@ -0,0 +1,122 @@
+# Phase 7.3: Security — ES256 Algorithm Upgrade - Context
+
+**Gathered:** 2026-06-05
+**Status:** Ready for planning
+
+
+## Phase Boundary
+
+Phase 7.3 delivers three auth-security upgrades in one cohesive change:
+
+1. **ES256 algorithm upgrade** — Replace HS256 with ECDSA P-256 (ES256) across all four JWT signing/verification sites in `services/auth.py`. Generate a P-256 key pair; store base64-encoded PEM keys as `JWT_PRIVATE_KEY` and `JWT_PUBLIC_KEY` env vars. A leaked public key cannot forge tokens.
+2. **Startup token rotation** — On first boot after the ES256 deploy, bulk-revoke all existing `RefreshToken` rows (set `revoked=True`). Detect the migration via the `system_settings` table; update the stored `jwt_algorithm` value after rotation so subsequent restarts do not re-revoke.
+3. **"Remember me" session duration** — Default refresh token TTL changes from 30 days to 16 hours. A `remember_me: bool = False` param on the login endpoint (and a checkbox in `LoginView.vue`) opts the user into the existing 30-day TTL. Access token TTL stays at 15 minutes (CLAUDE.md non-negotiable).
+
+No other changes are in scope for this phase.
+
+
+
+
+## Implementation Decisions
+
+### Key Format and Storage
+- **D-01:** Both `JWT_PRIVATE_KEY` and `JWT_PUBLIC_KEY` are **required** env vars. Both store the PEM key **base64-encoded** as a single line — shell-safe, no newline-escape complications. `config.py` decodes each value before passing to PyJWT.
+- **D-02:** `create_access_token` and `create_password_reset_token` sign with the private key using `algorithm="ES256"`. `decode_access_token` and `decode_password_reset_token` verify with the public key using `algorithms=["ES256"]`.
+- **D-03:** `settings.secret_key` is **removed from all JWT code** after ES256 is in place. It is no longer used for JWT signing. The `SECRET_KEY` env var stays in docker-compose for any future non-JWT HMAC needs (Phase 7.4 fingerprinting), but `services/auth.py` must not reference it.
+
+### Startup Token Rotation
+- **D-04:** On FastAPI `lifespan` startup, compare the `jwt_algorithm` key in the `system_settings` table (or its absence) against the current algorithm `"ES256"`. If the stored value differs (e.g., was `"HS256"` or is missing), bulk-revoke all `RefreshToken` rows by executing `UPDATE refresh_tokens SET revoked = true WHERE revoked = false`, then upsert `jwt_algorithm = "ES256"` into `system_settings`. This is idempotent — safe to restart.
+- **D-05:** Use the existing `system_settings` upsert pattern from `backend/services/ai_config.py` (`seed_system_settings_from_env`) as the reference for reading/writing system settings in the startup hook.
+
+### Key Generation Workflow
+- **D-06:** Document key generation as a **Python one-liner** in `README.md` using the `cryptography` library (already a project dependency). The snippet prints both base64-encoded keys ready to paste into `.env`. No extra tooling required.
+- **D-07:** `docker-compose.yml` references `${JWT_PRIVATE_KEY}` and `${JWT_PUBLIC_KEY}` from the `.env` file — same pattern as the existing `${SECRET_KEY}`. No placeholder values in the repo.
+
+### Session Duration / "Remember Me"
+- **D-08:** Access token TTL: **15 minutes** — unchanged and non-negotiable (CLAUDE.md).
+- **D-09:** Default refresh token TTL: **16 hours** (new config setting `refresh_token_expire_hours: int = 16`). This guarantees session expiry overnight and covers a full workday.
+- **D-10:** "Remember me" opt-in: **30 days** (existing `refresh_token_expire_days: int = 30` kept as-is). When `remember_me=True` is passed to the login endpoint, `create_refresh_token` uses the 30-day TTL; otherwise it uses 16 hours.
+- **D-11:** `POST /api/auth/login` request body gains `remember_me: bool = False`. `create_refresh_token` gains `remember_me: bool = False` param — selects between `timedelta(hours=settings.refresh_token_expire_hours)` and `timedelta(days=settings.refresh_token_expire_days)`.
+- **D-12:** `LoginView.vue` gains a "Remember me for 30 days" checkbox that passes `remember_me` to `authStore.login()`. The store passes it through to the API call.
+
+
+
+
+## Canonical References
+
+**Downstream agents MUST read these before planning or implementing.**
+
+### JWT signing — target code (4 sites to change)
+- `backend/services/auth.py` lines 84–150 — `create_access_token` (line 86), `decode_access_token` (line 102), `create_password_reset_token` (line 121), `decode_password_reset_token` (line 135); all currently use `settings.secret_key` / `"HS256"`
+
+### Config — where new settings land
+- `backend/config.py` line 31 — current `secret_key: str = "CHANGEME"`; add `jwt_private_key: str`, `jwt_public_key: str`, `refresh_token_expire_hours: int = 16` here
+
+### Startup hook — where rotation logic goes
+- `backend/main.py` line 136 — `lifespan` function; the ES256 rotation check goes inside this existing startup block, after the Redis check and before the AI seed
+- `backend/services/ai_config.py` — reference implementation for reading/writing `system_settings` key-value pairs at startup (`seed_system_settings_from_env` pattern)
+
+### Models — token and settings tables
+- `backend/db/models.py` line 87 — `RefreshToken` model with `revoked: Mapped[bool]` column (line 102) — target for bulk-revoke UPDATE
+- `backend/db/models.py` line 340 — `SystemSettings` model — storage for `jwt_algorithm` detection key
+
+### Login endpoint — remember_me param
+- `backend/api/auth.py` — `POST /api/auth/login` handler; add `remember_me: bool = False` to the request schema and pass it through to `create_refresh_token`
+
+### Frontend login form
+- `frontend/src/views/auth/LoginView.vue` line 228 — `authStore.login(email, password)` call; extend to pass `remember_me` bool from new checkbox
+
+### Security requirement
+- `CLAUDE.md` §"Login token hardening" — access token TTL 15 min max, ES256 asymmetric algorithm, refresh token rotation; this phase enforces all three
+- `.planning/codebase/CONCERNS.md` §"JWT Algorithm Downgrade: HS256 Instead of ES256" — full risk description and fix approach
+
+### docker-compose env var pattern
+- `docker-compose.yml` lines ~60–65 — existing `SECRET_KEY=${SECRET_KEY}` pattern; `JWT_PRIVATE_KEY` and `JWT_PUBLIC_KEY` follow the same form
+
+
+
+
+## Existing Code Insights
+
+### Reusable Assets
+- `lifespan` function in `backend/main.py:136` — already has a startup block with DB session, Redis check, and AI seed; the ES256 rotation check slots in as another startup task in the same block
+- `SystemSettings` model + upsert pattern in `backend/services/ai_config.py` — exact pattern for reading and writing a key-value entry in `system_settings` at startup; copy this to avoid re-inventing the upsert
+- `RefreshToken.revoked` column — already exists (migrations/0001); no schema change needed for bulk revoke
+
+### Established Patterns
+- Base64-encoded keys: `config.py` already handles `minio_secret_key` and similar string secrets from env — apply the same `base64.b64decode(settings.jwt_private_key).decode()` pattern before constructing the `EllipticCurvePrivateKey` for PyJWT
+- Token hash: refresh tokens stored as `sha256(raw.encode()).hexdigest()` — unchanged by this phase; only the *access* token algorithm changes
+- `create_refresh_token` in `services/auth.py` currently uses `timedelta(days=settings.refresh_token_expire_days)` — add a `remember_me` param that selects between hours and days TTL
+
+### Integration Points
+- `backend/services/auth.py` — primary change site; 4 signing/decode functions + `create_refresh_token` TTL param
+- `backend/config.py` — add 3 new settings; `secret_key` stays but JWT code stops referencing it
+- `backend/main.py:lifespan` — add startup rotation check (5–10 lines)
+- `backend/api/auth.py` — login endpoint gains `remember_me: bool = False`; `create_refresh_token` call passes it through
+- `frontend/src/views/auth/LoginView.vue` — add checkbox, pass `remember_me` to store/API
+- `frontend/src/stores/auth.js` (or `.ts`) — `login()` action gains `remember_me` param and passes it to the API
+
+
+
+
+## Specific Ideas
+
+- The startup rotation bulk UPDATE should use a raw SQL `UPDATE refresh_tokens SET revoked = true WHERE revoked = false` executed via `session.execute(text(...))` for efficiency — avoids loading all rows into Python (same pattern as other bulk updates in the codebase).
+- The `system_settings` key for algorithm tracking: use `key="jwt_algorithm"`, `value="ES256"`, provider/model fields `NULL` or `""` (check existing SystemSettings schema for nullable constraints before inserting).
+- PyJWT ES256 usage: `jwt.encode(payload, private_key_pem_str, algorithm="ES256")` and `jwt.decode(token, public_key_pem_str, algorithms=["ES256"])` — PyJWT accepts PEM strings directly; no need to use the `cryptography` key objects directly in the jwt calls.
+- The "Remember me" checkbox label: "Stay signed in for 30 days" — clearer than "Remember me" about what it actually does.
+
+
+
+
+## Deferred Ideas
+
+- **Phase 7.4 — Token fingerprinting / token binding**: Add `fgp` (fingerprint) claim = `hmac(key, User-Agent + Accept-Language)[:16]`; validate in `get_current_user`. `SECRET_KEY` may serve as the HMAC key here. Tracked in `.planning/codebase/CONCERNS.md` §"No Token Fingerprint / Token Binding".
+- **Key rotation ceremony**: A process for rotating the P-256 key pair in production (dual-key overlap period, gradual rollout) — not needed for the initial upgrade but worth documenting when this goes to production.
+
+
+
+---
+
+*Phase: 07.3-security-es256-algorithm-upgrade-inserted*
+*Context gathered: 2026-06-05*
diff --git a/.planning/phases/07.3-security-es256-algorithm-upgrade-inserted/07.3-DISCUSSION-LOG.md b/.planning/phases/07.3-security-es256-algorithm-upgrade-inserted/07.3-DISCUSSION-LOG.md
new file mode 100644
index 0000000..c2f41a7
--- /dev/null
+++ b/.planning/phases/07.3-security-es256-algorithm-upgrade-inserted/07.3-DISCUSSION-LOG.md
@@ -0,0 +1,135 @@
+# Phase 7.3: Security — ES256 Algorithm Upgrade - Discussion Log
+
+> **Audit trail only.** Do not use as input to planning, research, or execution agents.
+> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
+
+**Date:** 2026-06-05
+**Phase:** 07.3-security-es256-algorithm-upgrade-inserted
+**Areas discussed:** Key format in env vars, Startup refresh-token rotation, Key generation workflow, JWT TTL / Remember Me
+
+---
+
+## Key Format in Env Vars
+
+### Q1: How should P-256 PEM keys be stored in env vars?
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Base64-encoded | Single line, shell-safe, decoded in config.py | ✓ |
+| Escaped `\n` in .env | python-dotenv expands `\n`; loader-dependent | |
+| Multiline .env block | Breaks in docker-compose environment: block | |
+
+**User's choice:** Base64-encoded
+**Notes:** Standard 12-factor approach; no loader ambiguity.
+
+---
+
+### Q2: Both keys required, or derive public from private at startup?
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Both required env vars | Clean separation; workers can hold public key only | ✓ |
+| Private key only — derive public | Fewer env vars but more magic in config.py | |
+
+**User's choice:** Both required env vars
+
+---
+
+### Q3: What happens to SECRET_KEY after ES256?
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Remove from JWT code | SECRET_KEY no longer used for signing; stays in env for future HMAC | ✓ |
+| Repurpose as HMAC key for Phase 7.4 | Forward-compatible but couples phases | |
+
+**User's choice:** Remove SECRET_KEY from JWT code
+
+---
+
+## Startup Refresh-Token Rotation
+
+### Q1: How to implement startup rotation?
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| DELETE all refresh_tokens | Simple, one-shot; no schema change | |
+| Bulk revoke (set revoked=True) | Cleaner audit trail; same user impact | ✓ |
+| Let natural expiry handle it | Doesn't satisfy ROADMAP requirement | |
+
+**User's choice:** Bulk revoke (set revoked=True)
+
+---
+
+### Q2: How to detect "first boot after ES256 migration"?
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Check system_settings table | Stores `jwt_algorithm`; idempotent on restart | ✓ |
+| New env var JWT_TOKEN_GENERATION=N | Simple but requires operator bump on deploy | |
+| One-time Alembic migration | Runs at migrate time, not startup | |
+
+**User's choice:** Check system_settings table (`jwt_algorithm` key)
+
+---
+
+## Key Generation Workflow
+
+### Q1: How should developers generate the key pair?
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Python one-liner in README | Uses existing `cryptography` dep; no extra tooling | ✓ |
+| openssl CLI command | Familiar to ops but multi-step, requires openssl | |
+| Auto-generate on first boot | Convenient for dev; dangerous in prod (ephemeral keys) | |
+
+**User's choice:** Python one-liner documented in README
+
+---
+
+### Q2: docker-compose key placeholder vs. .env reference?
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| .env file reference only | Follows existing ${SECRET_KEY} pattern | ✓ |
+| Placeholder comment in docker-compose | Helpful but could mislead | |
+
+**User's choice:** .env file reference only
+
+---
+
+## JWT TTL / Remember Me
+
+### Q1 (freeform): What token should default to 12 hours?
+
+User note: "JWT Tokens should only be valid for 12 hours and the user can opt-in to create a token which is valid for 30 days."
+
+Asked for clarification on whether this applied to the access token or refresh token. User asked: "What do you think is the most secure way to handle tokens?"
+
+**Claude's recommendation:** Keep access token at 15 min (CLAUDE.md requirement). Change refresh token default to 16–24 hours with explicit "remember me" opt-in for 30 days.
+
+**User's decision:** 16 hours — covers a full workday; guarantees session is revoked overnight.
+
+---
+
+### Q2: Fold "remember me" into Phase 7.3 or separate phase?
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Fold into Phase 7.3 | Already touching token creation code; incremental work | ✓ |
+| Separate phase 7.5 | Keeps 7.3 purely as ES256 upgrade | |
+
+**User's choice:** Fold into Phase 7.3
+
+---
+
+## Claude's Discretion
+
+- PyJWT API form: `jwt.encode(payload, pem_str, algorithm="ES256")` — PEM strings passed directly, no key object conversion needed
+- Bulk revoke via raw SQL `UPDATE` rather than ORM row-by-row — efficiency decision
+- "Stay signed in for 30 days" as checkbox label (clearer than "Remember me")
+- system_settings key name: `jwt_algorithm`, value `"ES256"`
+
+## Deferred Ideas
+
+- **Phase 7.4:** Token fingerprinting / token binding (`fgp` claim = HMAC of User-Agent + Accept-Language)
+- **Key rotation ceremony:** Dual-key overlap process for rotating P-256 keys in production — deferred until production hardening milestone