ProfilesFile.read/1: drop [:safe] flag, trust our own files
The persisted grid_data files contain atoms (:base_m, :top_m, :thickness_m, :min_freq_ghz, :native_min_gradient, :ducts, etc.) that live in modules which aren't eagerly loaded during application start. warm_grid_cache_from_latest_profile runs in Application.start/2 before those modules get pulled in by their first call sites, so [:safe] was rejecting our own legitimate files at boot — every weather page hit crashed with "invalid or unsafe external representation of a term". The whole point of [:safe] is to refuse atoms from untrusted external ETF input. ProfilesFile.write!/2 in this same module is the only writer of these files, so the input is trusted by definition — drop the flag. The test additions round-trip the production data shape including duct fields as a regression guard.
This commit is contained in:
parent
04c8a92db8
commit
26015ce8ea
2 changed files with 52 additions and 1 deletions
|
|
@ -67,11 +67,21 @@ defmodule Microwaveprop.Propagation.ProfilesFile do
|
|||
`{:ok, %{{lat, lon} => profile}}` or `{:error, :enoent}` if the file
|
||||
doesn't exist. Used by `Microwaveprop.Weather` to warm `GridCache`
|
||||
on pod startup without touching the database.
|
||||
|
||||
Does *not* pass `[:safe]` to `binary_to_term/1`. The file was written
|
||||
by `write!/2` in the same code base, so we trust it. The previous
|
||||
`[:safe]` flag was rejecting our own files at app-startup warm time
|
||||
because some atoms in the persisted profile (`:base_m`, `:top_m`,
|
||||
`:thickness_m`, `:min_freq_ghz`, `:native_min_gradient`, etc.) live
|
||||
in modules that aren't eagerly loaded during boot, so they weren't
|
||||
in the runtime atom table when `warm_grid_cache_from_latest_profile`
|
||||
ran. The fix is not "load more modules eagerly" — it's "trust files
|
||||
we wrote ourselves."
|
||||
"""
|
||||
@spec read(DateTime.t()) :: {:ok, %{{float(), float()} => map()}} | {:error, :enoent}
|
||||
def read(%DateTime{} = valid_time) do
|
||||
case File.read(path_for(valid_time)) do
|
||||
{:ok, binary} -> {:ok, :erlang.binary_to_term(binary, [:safe])}
|
||||
{:ok, binary} -> {:ok, :erlang.binary_to_term(binary)}
|
||||
{:error, _} -> {:error, :enoent}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -56,6 +56,47 @@ defmodule Microwaveprop.Propagation.ProfilesFileTest do
|
|||
test "returns :enoent when no file exists" do
|
||||
assert ProfilesFile.read(~U[2026-04-14 18:00:00Z]) == {:error, :enoent}
|
||||
end
|
||||
|
||||
test "round-trips the full production data shape including duct fields" do
|
||||
valid_time = ~U[2026-04-14 18:00:00Z]
|
||||
|
||||
grid_data = %{
|
||||
{32.75, -97.125} => %{
|
||||
surface_temp_c: 25.0,
|
||||
surface_dewpoint_c: 15.0,
|
||||
surface_pressure_mb: 1013.25,
|
||||
hpbl_m: 750.0,
|
||||
pwat_mm: 32.5,
|
||||
run_time: ~U[2026-04-14 17:00:00Z],
|
||||
precip_mm: 0.0,
|
||||
wind_u: 3.2,
|
||||
wind_v: -1.7,
|
||||
cloud_cover_pct: 25.0,
|
||||
native_min_gradient: -78.4,
|
||||
best_duct_freq_ghz: 24.0,
|
||||
duct_count: 1,
|
||||
max_duct_thickness_m: 200.0,
|
||||
ducts: [
|
||||
%{base_m: 200.0, top_m: 400.0, thickness_m: 200.0, min_freq_ghz: 10.0}
|
||||
],
|
||||
profile: [
|
||||
%{"pres" => 1000.0, "tmpc" => 25.0, "dwpc" => 15.0, "hght" => 50.0}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
ProfilesFile.write!(valid_time, grid_data)
|
||||
|
||||
assert {:ok, round_tripped} = ProfilesFile.read(valid_time)
|
||||
|
||||
point = round_tripped[{32.75, -97.125}]
|
||||
assert point[:surface_temp_c] == 25.0
|
||||
assert point[:run_time] == ~U[2026-04-14 17:00:00Z]
|
||||
assert [duct] = point[:ducts]
|
||||
assert duct[:base_m] == 200.0
|
||||
assert duct[:thickness_m] == 200.0
|
||||
assert duct[:min_freq_ghz] == 10.0
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_valid_times/0" do
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue