260916 – Dev update
24 commits, 94 files, ~15k insertions (~1.2k deletions) — though ~10k of that is a single generated knowledge-graph refresh, so ~4.8k lines of hand-written change. No features newly Implemented this window. Six bug fixes to already-shipped features, one new item defined and queued, three new device/UAT sign-off checks raised, and one promotion of the accumulated work to UAT. Two of the six fixes came from production or TestFlight telemetry rather than from a test.
🧠 The relevance classifier: a regression found, repaired, and now 7.6x faster than before the work started
The first landing of the airspace relevance optimisation regressed the exact case it was meant to improve. Measured on real Hermes with the UK bundle, a five-leg route over Newcastle went from 3,742 ms before the work to 7,641 ms after it. It now runs in 554 ms, with allocation per call down from 2.69 GB to 0.33 GB (F853)
The cause was a segment index that rasterised each route edge’s bounding box into 250 m cells. The bundle’s longest edge is 209 km, whose box covers ~700,000 cells — one airspace complex alone generated 405,362 index insertions. It now walks the edge itself, and skips the index entirely below the size where brute force is simply faster
Four fail-safes were silently unverified, because an iOS-safety change to the match-all filter left the test evaluator unable to read it. The out-of-scope fail-safe was enumerating the complement of the viewport into every filter; it now reports the bounded in-scope set and negates it — and above the cap it abandons scoping rather than truncating it, because a truncated list would tier an out-of-scope zone as remote, which the proximity rules forbid
Two smaller corrections with real consequences: the scope key was unquantised, firing a full adjacency pass on every animation frame during a pan or pinch; and the ground-speed buffer was set at 101 kt, under-buffering a GA tailwind by half. The promote-fast/demote-slow hysteresis was documented but had never actually been implemented
💾 4 GiB of disk writes per day, gone
A TestFlight resource report recorded 4,294.98 MB of file-backed memory dirtied in 3 h 45 m, against the report’s own 4,294.97 MB limit — the app spent a full day’s iOS write allowance in under four hours, and since the report fires at the trip point, that figure is a floor, not a total
Every airspace cell was being inflated to a temp file and deleted again, with nothing persisting the inflated form — so each re-admission paid a full gunzip-to-disk. Re-admission is constant by design: a 16-cell cache with three evictors against a twice-per-second position tick. Both readers now use the native gzip stream that terrain has run in production since F1256 (F1257)
The cost was invisible to the app’s own accounting twice over: the cache directory sits outside disk-cap accounting, and the cap’s own filter keyed on the compressed extension before summing bytes. Also fixed, one layer down: an aborted stream leaked a native handle against a cap of eight
🩺 A production 500 on the currency dashboard, and two crash sites the traceback never named
The currency endpoint returned 500 for any pilot whose cache row was read back from the database rather than freshly written — 274 occurrences in 48 hours on production. Two real DATE columns were declared as 10-character strings in the model, so the database’s own date type reached validation unconverted (F69)
Fixed at the model, not coerced at the call site, and with no migration: the columns were always correct, only the model was wrong, so production rows are untouched and the wire format is unchanged — verified against the mobile client, no app change needed
Sweeping every reader of those two columns found two further failures the traceback did not reach: the medical-expiry alert dispatcher had the same defect, and the daily scan swallows per-pilot exceptions — so on production it would have sent no medical-expiry alerts at all, silently
This is the third instance of one class of defect, which is why it now has an item of its own (below)
✅ The external smoke suite reached UAT for the first time
All three authenticated journeys now run end to end: 33 of 34 passing, 1 advisory, against 8 of 23 before. The cause was not the missing test account — the perimeter gate was installed as client-level auth, and the HTTP client applies that after per-request headers, so it overwrote the bearer token on every authenticated call. The gateway then stripped what was left, and the request arrived with no credential at all (F179)
Login and refresh had always passed, because their credentials travel in the body — which is precisely why this hid for so long. The gate is now challenged once and carried as a session cookie, leaving the auth header free
Four fixtures had never met the real API, because the journeys had never executed — wrong envelope on track points, wrong field names and units on the route check, and a glider sending a fuel type the validator does not exempt
🔊 Audio annunciation policy closed out
The two defects found during promotion are fixed and verified: a stale lint tripwire, and an untested session-leak guard on unmount inside a claim, now fully covered (F997)
🧪 CI made honest rather than lenient
Three consecutive CI failures, each on a different suite, all the same timeout. Measured rather than guessed: the tests are fast (1.4 s locally) and the runner is slow — the full suite takes 537 s locally and 1,560–1,856 s in CI, a ~3.5x penalty that lands a 1.4 s test right on the 5 s default. The timeout is now explicit at 20 s. No coverage, lint or type gate was touched, and nothing was skipped or suppressed — a failing assertion still fails, a hung test still fails, just later (F1239, F1285)
The underlying races were fixed as a population, not one per CI round trip: all eight test sites sharing that shape now flush the promise and its render before asserting, with the original backstop kept so longer async chains stay covered
📐 New: one convention for storing time, written down and gated
The project has a rigorous, gate-enforced convention for physical units and none at all for storing time. Measured today: the schema holds 153 epoch columns, 111 datetime, 29 date — and 14 columns disagree with their own model. Three of those are on live endpoints and carry the exact defect that produced the 274 production 500s above (F1308)
The convention is now agreed — epoch for instants, calendar dates for dates, instant-plus-timezone where a pilot sees local time, and never the 2038-limited type, which cannot hold imported logbook history anyway. Phase one is an instructions file and a static gate: zero data risk, no production code path
It exists because the test suite structurally cannot catch this class — the test database builds its tables from the models, so it always agrees with whichever side is wrong. The four files in today’s currency fix were at 100% line and branch coverage before the defect was found in production
🚀 Promoted to UAT
One promotion carrying the water-routing reach test, the mobile memory chain (F1254–F1265), the hangar and aircraft-checklist work, the MapLibre 6 web map fix and today’s currency fix — plus release-gate remediation done as part of the promotion: three water conformance vectors correctly tagged, four water modules brought to full coverage, and the two test races fixed at root cause (F1289, F1254–F1265, F1284, F1285, F935, F69)
Nothing shipped to production today. Three new sign-off checks are queued and need a device or UAT to close: the classifier’s on-device allocation and GC profile, the disk-writes report on a TestFlight build, and the currency endpoint against MariaDB (F853, F1257, F69)