# Externalize mission-planner OWM key + base URL; close AZ-482 source-scan gap

**Task**: AZ-499_mission_planner_weather_env
**Name**: mission-planner OWM env-var hardening
**Description**: Replace the hardcoded OpenWeatherMap API key and base URL in `mission-planner/src/services/WeatherService.ts` with Vite env vars (mirroring AZ-448 / AZ-449 on the main SPA), and close the AZ-482 source-scan gap that previously allowed the committed key to slip past the static check.
**Complexity**: 2 points
**Dependencies**: AZ-448 (Externalize OWM API key), AZ-449 (Externalize OWM base URL), AZ-482 (Secrets/banned-libs static check).
**Component**: 05_flights (mission-planner port-root)
**Tracker**: AZ-499
**Epic**: AZ-497

## Problem

`mission-planner/src/services/WeatherService.ts` lines 4–5 contain:

```ts
const apiKey = '335799082893fad97fa36118b131f919';
const url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}&units=metric`;
```

Two issues:

1. **Compromised secret in source**: a real OpenWeatherMap API key is committed and has been in git history. Anyone with read access to the repo (or to any past mirror) can grab and abuse it.
2. **Hygiene gap**: AZ-448/AZ-449 closed the same pattern on the main SPA (`src/features/flights/flightPlanUtils.ts`), and AZ-482 was supposed to keep the key out via a static check. But AZ-482's `owm_key_in_dist` kind only scans the post-build `dist/` artifact, not the source tree, and only the main SPA bundle (not `mission-planner/`). STC-S5 keeps `mission-planner/` out of `dist/`, so today the key never reaches the bundle — but it remains plainly visible in source and survives every test run.

## Outcome

- `mission-planner/src/services/WeatherService.ts` reads `VITE_OWM_API_KEY` and `VITE_OWM_BASE_URL` from `import.meta.env`; never references the literal key.
- `getWeatherData` returns `null` when `VITE_OWM_API_KEY` is unset (same fail-soft contract as AZ-448 on the main SPA).
- `mission-planner/.env.example` and `mission-planner/src/vite-env.d.ts` declare both vars.
- A new banned-deps kind `owm_key_in_source` scans `src/` AND `mission-planner/` for the (now-rotated) old key literal and any future hardcoded fallback. STC-S? wires it into `scripts/run-tests.sh --static-only`.
- The compromised key `335799082893fad97fa36118b131f919` is revoked at the OpenWeatherMap dashboard out-of-band, before this task closes. The revocation is a deliverable, not just a recommendation.

## Scope

### Included

- `mission-planner/src/services/WeatherService.ts`: replace the two literals with `import.meta.env.VITE_OWM_API_KEY` and `import.meta.env.VITE_OWM_BASE_URL`; when the key is unset, return `null` without calling `fetch`.
- `mission-planner/.env.example`: add `VITE_OWM_API_KEY=<your-openweathermap-api-key>` and `VITE_OWM_BASE_URL=https://api.openweathermap.org/data/2.5`; mirror the docstring style of the main `.env.example`.
- `mission-planner/src/vite-env.d.ts`: add `VITE_OWM_API_KEY?: string` and `VITE_OWM_BASE_URL?: string`.
- `tests/security/banned-deps.json`: add a new `owm_key_in_source` kind:
  - `ac`: NFT-SEC-09 (AC-1, source portion) — OpenWeatherMap key not present in source tree
  - `scope`: `src/ and mission-planner/ (production sources; tests excluded)`
  - `match`: `literal`
  - `patterns`: `["335799082893fad97fa36118b131f919"]`
- `scripts/run-tests.sh`: add a new static-check row (e.g., `STC-S6`) that wires the new kind via `node scripts/check-banned-deps.mjs --kind=owm_key_in_source`.
- `_docs/02_document/modules/mission-planner.md` (or the closest existing mission-planner doc): note the env-var dependency under the WeatherService entry.
- Manual out-of-band: revoke the compromised key at `https://home.openweathermap.org/api_keys`; provision the new key in CI/dev `.env.local` for mission-planner.

### Excluded

- The broader F1 mission-planner deduplication work — tracked under its own future epic per `_docs/02_tasks/_dependencies_table.md` notes; this task is narrow security hygiene, not the duplication fix.
- Adding tests for `getWeatherData`'s `WeatherData` mapping logic (existing behavior, no test coverage today; out of scope here).
- Changing `getWeatherData`'s public signature.

## Acceptance Criteria

**AC-1: Env-var resolved API key**
Given `VITE_OWM_API_KEY=abc123` at build time,
When `getWeatherData(lat, lon)` is invoked,
Then the outgoing `fetch` URL contains `appid=abc123` and `units=metric`.

**AC-2: Env-var resolved base URL**
Given `VITE_OWM_BASE_URL=https://example.test/data/2.5` at build time,
When `getWeatherData(lat, lon)` is invoked,
Then the outgoing `fetch` URL starts with `https://example.test/data/2.5/weather?`.

**AC-3: Fail-soft when key is unset**
Given `VITE_OWM_API_KEY` is unset at build time,
When `getWeatherData(lat, lon)` is invoked,
Then no `fetch` is made and the function returns `null`.

**AC-4: Default base URL when only the URL var is unset**
Given `VITE_OWM_API_KEY` is set AND `VITE_OWM_BASE_URL` is unset at build time,
When `getWeatherData(lat, lon)` is invoked,
Then the outgoing URL falls back to `https://api.openweathermap.org/data/2.5/weather?...`.

**AC-5: Source-scan static check**
Given the static-only profile runs (`scripts/run-tests.sh --static-only`),
When the new `owm_key_in_source` check executes,
Then a fresh introduction of the literal `335799082893fad97fa36118b131f919` anywhere under `src/` or `mission-planner/` (excluding test files) FAILS the build; the migrated codebase passes.

**AC-6: Type declarations**
Given a TypeScript build of `mission-planner/`,
Then `ImportMetaEnv` includes `VITE_OWM_API_KEY?: string` and `VITE_OWM_BASE_URL?: string`.

**AC-7: Key revocation (deliverable)**
The previously-committed key `335799082893fad97fa36118b131f919` is revoked at the OpenWeatherMap dashboard. Closure of this AC is recorded in the implementation report by including a screenshot or a dashboard URL showing the key disabled — to keep the AC verifiable without re-exposing the new key.

## Non-Functional Requirements

**Security**
- The new key MUST never be committed; it lives only in `.env.local` (gitignored) for dev and in CI secrets for builds.
- The old key MUST be revoked at the OWM dashboard before this task is marked Done.

**Compatibility**
- `WeatherService.getWeatherData(lat, lon)` signature is preserved; callers see no behavioral change beyond `null` returned when the key is unset.

## Unit Tests

| AC Ref | What to Test                                          | Required Outcome                                  |
|--------|------------------------------------------------------|---------------------------------------------------|
| AC-1   | env mocked with key only                             | URL contains `appid=<key>&units=metric`           |
| AC-2   | env mocked with custom base URL                       | URL prefix matches the env-set base               |
| AC-3   | env mocked with key unset                            | `getWeatherData` returns `null`; no `fetch` call  |
| AC-4   | env mocked with key set, base URL unset              | URL prefix = default production OWM base          |
| AC-6   | TS compile against `ImportMetaEnv`                   | Compiles; new keys present, no `any` widening     |

## Blackbox Tests

| AC Ref | Initial Data/Conditions                        | What to Test                                                 | Expected Behavior                  | NFR References |
|--------|------------------------------------------------|--------------------------------------------------------------|------------------------------------|----------------|
| AC-5   | Static-only profile run                        | `check-banned-deps.mjs --kind=owm_key_in_source`             | Pass on clean tree; fail on regression | NFT-SEC-09     |

## Constraints

- Do NOT change `WeatherService.getWeatherData`'s public signature.
- Do NOT add a new dependency to `mission-planner/package.json`. The change is configuration-only.
- The new banned-deps kind MUST follow the same JSON shape as existing entries in `tests/security/banned-deps.json` so `check-banned-deps.mjs` doesn't need branching logic.

## Risks & Mitigation

**Risk 1: New key leakage during rollout**
- *Risk*: The replacement OWM key could be committed by mistake when devs set it up locally.
- *Mitigation*: The new `owm_key_in_source` static check catches any literal value in source. Pair with a pre-commit hook (out of scope; flagged as a future improvement) for local enforcement.

**Risk 2: Mission-planner has no test runner today**
- *Risk*: `mission-planner/` doesn't have Vitest/Jest wired (module-layout.md note: tests TBD). The unit-test ACs above need a minimal test harness.
- *Mitigation*: Either (a) wire a minimal Vitest setup for `mission-planner/` (treat as a small in-task investment), or (b) move the unit-test ACs into integration coverage on the main SPA's harness if `mission-planner` shares a build context. Choose at implementation time; the simpler option wins.

**Risk 3: Revocation timing**
- *Risk*: If the old key is revoked before this code lands, every mission-planner build using the old key (dev/CI) breaks.
- *Mitigation*: Rotate the key AT THE SAME TIME the code change is merged: PR description includes the revocation timing; dev `.env.local` files updated in lock-step with merge.

## Contract

(Omitted — this task does not produce or consume an internal suite contract; OpenWeatherMap is an external 3rd-party API and its shape is owned by them.)

### Document Dependencies

- `_docs/02_tasks/done/AZ-448_refactor_owm_api_key.md` — main-SPA pattern this task mirrors.
- `_docs/02_tasks/done/AZ-449_refactor_owm_base_url.md` — same pattern for the base URL.
- `_docs/02_tasks/done/AZ-482_test_secrets_and_banned_libs.md` — the static-check scaffolding this task extends.
