Review story 1.5: provisioning execution passes all ACs — Epic 1 complete
Fix portal.py error handling so validate_cache failures return retry HTML while kill_wifi ProvisioningError propagates (re-raise) per AC4. All 56 tests pass. Update sprint-status.yaml: 1-5 → done, epic-1 → done. Append story 1.5 deferred items to deferred-work.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# Story 1.5: Provisioning Execution — Tile Download, Cache Validation & WiFi Kill
|
# Story 1.5: Provisioning Execution — Tile Download, Cache Validation & WiFi Kill
|
||||||
|
|
||||||
Status: review
|
Status: done
|
||||||
|
|
||||||
## Story
|
## Story
|
||||||
|
|
||||||
|
|||||||
@@ -54,3 +54,32 @@ Description: Nominatim geocoding verified in tests with mocks only; real geocodi
|
|||||||
Story: `1-4-location-resolution-icao-and-address`
|
Story: `1-4-location-resolution-icao-and-address`
|
||||||
Category: Technical debt
|
Category: Technical debt
|
||||||
Description: ICAO heuristic (`len(query) == 4 and query.isalpha()`) may misclassify 4-letter words (e.g. "BATH", "YORK") as ICAO codes, causing them to be looked up in `airports.csv` before falling back to Nominatim. Acceptable for MVP given the provisioning context, but noted for future hardening (e.g. validate against a known ICAO prefix list).
|
Description: ICAO heuristic (`len(query) == 4 and query.isalpha()`) may misclassify 4-letter words (e.g. "BATH", "YORK") as ICAO codes, causing them to be looked up in `airports.csv` before falling back to Nominatim. Acceptable for MVP given the provisioning context, but noted for future hardening (e.g. validate against a known ICAO prefix list).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Story 1.5: Provisioning Execution — Tile Download, Cache Validation & WiFi Kill
|
||||||
|
|
||||||
|
### [1-5] nmcli / NetworkManager dependency
|
||||||
|
Story: `1-5-provisioning-execution-tile-download-cache-validation-and-wifi-kill`
|
||||||
|
Category: Infrastructure/environment
|
||||||
|
Description: `nmcli` requires NetworkManager to be installed and running on the Pi; the `wlan0` interface must support managed mode. Raspberry Pi OS Lite uses `dhcpcd` by default — NetworkManager must be installed and enabled before `join_home_wifi()` will work.
|
||||||
|
|
||||||
|
### [1-5] rfkill permission requirement
|
||||||
|
Story: `1-5-provisioning-execution-tile-download-cache-validation-and-wifi-kill`
|
||||||
|
Category: Infrastructure/environment
|
||||||
|
Description: `rfkill block wifi` requires the process to have permission to block the WiFi interface. The user running the provisioning service must be root or have the `CAP_NET_ADMIN` capability. The systemd unit must be configured accordingly.
|
||||||
|
|
||||||
|
### [1-5] OSM tile download and OpenAIP API runtime verification
|
||||||
|
Story: `1-5-provisioning-execution-tile-download-cache-validation-and-wifi-kill`
|
||||||
|
Category: Runtime verification
|
||||||
|
Description: OSM tile download, OpenAIP API call, and the full provisioning sequence (WiFi join → tile download → airspace download → validate → write config → rfkill) can only be end-to-end verified on device with real network access. All tests use mocks only.
|
||||||
|
|
||||||
|
### [1-5] provision.py port 80 requires root
|
||||||
|
Story: `1-5-provisioning-execution-tile-download-cache-validation-and-wifi-kill`
|
||||||
|
Category: Infrastructure/environment
|
||||||
|
Description: `provision.py` calls `app.run(port=80)` which requires root privileges or the `CAP_NET_BIND_SERVICE` capability to bind to a port below 1024. The systemd unit for the provisioning service must run as root or be granted the appropriate capability.
|
||||||
|
|
||||||
|
### [1-5] Synchronous POST /submit — browser waits during provisioning
|
||||||
|
Story: `1-5-provisioning-execution-tile-download-cache-validation-and-wifi-kill`
|
||||||
|
Category: Technical debt
|
||||||
|
Description: The `POST /submit` handler is fully synchronous — the browser connection stays open while tile download, airspace download, and cache validation complete (potentially 2–5 minutes). This is acceptable for MVP but a streaming response (using `flask.stream_with_context` or a background thread with server-sent events) would improve UX by allowing the browser to render progress feedback without holding an open connection.
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
# - Dev moves story to 'review', then runs code-review (fresh context, different LLM recommended)
|
# - Dev moves story to 'review', then runs code-review (fresh context, different LLM recommended)
|
||||||
|
|
||||||
generated: 2026-04-22
|
generated: 2026-04-22
|
||||||
last_updated: 2026-04-22 # 1-5 story file created, marked ready-for-dev
|
last_updated: 2026-04-22 # 1-5 review complete, epic-1 done
|
||||||
project: planeMapper
|
project: planeMapper
|
||||||
project_key: NOKEY
|
project_key: NOKEY
|
||||||
tracking_system: file-system
|
tracking_system: file-system
|
||||||
@@ -43,12 +43,12 @@ story_location: _bmad-output/implementation-artifacts
|
|||||||
|
|
||||||
development_status:
|
development_status:
|
||||||
# Epic 1: Device Setup & Provisioning
|
# Epic 1: Device Setup & Provisioning
|
||||||
epic-1: in-progress
|
epic-1: done
|
||||||
1-1-project-scaffold-and-verified-entry-points: done
|
1-1-project-scaffold-and-verified-entry-points: done
|
||||||
1-2-configuration-read-write-wipe: done
|
1-2-configuration-read-write-wipe: done
|
||||||
1-3-wifi-hotspot-and-captive-portal-form: done
|
1-3-wifi-hotspot-and-captive-portal-form: done
|
||||||
1-4-location-resolution-icao-and-address: done
|
1-4-location-resolution-icao-and-address: done
|
||||||
1-5-provisioning-execution-tile-download-cache-validation-and-wifi-kill: review
|
1-5-provisioning-execution-tile-download-cache-validation-and-wifi-kill: done
|
||||||
epic-1-retrospective: optional
|
epic-1-retrospective: optional
|
||||||
|
|
||||||
# Epic 2: Live Radar Display
|
# Epic 2: Live Radar Display
|
||||||
|
|||||||
@@ -110,11 +110,17 @@ def _run_provisioning(
|
|||||||
wifi_ssid: str,
|
wifi_ssid: str,
|
||||||
wifi_password: str,
|
wifi_password: str,
|
||||||
) -> str:
|
) -> str:
|
||||||
try:
|
|
||||||
wifi.join_home_wifi(wifi_ssid, wifi_password)
|
wifi.join_home_wifi(wifi_ssid, wifi_password)
|
||||||
tiles.download_and_composite(confirmed_lat, confirmed_lon, radius)
|
tiles.download_and_composite(confirmed_lat, confirmed_lon, radius)
|
||||||
airspace.download(confirmed_lat, confirmed_lon, radius)
|
airspace.download(confirmed_lat, confirmed_lon, radius)
|
||||||
|
try:
|
||||||
tiles.validate_cache()
|
tiles.validate_cache()
|
||||||
|
except ProvisioningError as e:
|
||||||
|
log.error("cache validation failed: %s", e)
|
||||||
|
return (
|
||||||
|
f"<html><body><p>Cache validation failed: {e}. "
|
||||||
|
f"<a href='/'>Try again</a></p></body></html>"
|
||||||
|
)
|
||||||
config.write(
|
config.write(
|
||||||
{
|
{
|
||||||
"home_lat": confirmed_lat,
|
"home_lat": confirmed_lat,
|
||||||
@@ -131,11 +137,6 @@ def _run_provisioning(
|
|||||||
"<h1>Setup complete. The device will now start displaying radar.</h1>"
|
"<h1>Setup complete. The device will now start displaying radar.</h1>"
|
||||||
"</body></html>"
|
"</body></html>"
|
||||||
)
|
)
|
||||||
except ProvisioningError as e:
|
|
||||||
log.error("provisioning failed: %s", e)
|
|
||||||
return (
|
|
||||||
f"<html><body><p>Provisioning failed: {e}. <a href='/'>Try again</a></p></body></html>"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/submit", methods=["POST"])
|
@app.route("/submit", methods=["POST"])
|
||||||
|
|||||||
Reference in New Issue
Block a user