fix: simulator audit remediation - #40
Merged
Merged
Conversation
Four inputs reached a GO the evidence did not support. accepted_warning_codes could waive the zero-zone and coverage-missing codes, so the file supplying an asset was the file waiving the check on it. A NaN geofence bound disarmed the zone rather than failing, because every comparison against NaN is false. The relevance envelope was derived from the vehicle's own energy figures, so a 3 W cruise power stretched it to 19 440 km and certified assets on another continent, and a small pack shrank it enough to reject a real mast 6.45 km away. Out-of-coverage wind clamped to the nearest edge, so a forecast for Kansas answered a route over the Netherlands. Calibration provenance was self-asserted the same way: one line of YAML, or a profile naming no real flight, cleared ENERGY_MODEL_UNCALIBRATED. Credit now comes only from supplied traces that reproduce the profile's dataset id and refit its parameters. Without them the profile still applies and the artifact records the lineage as unverified, so the run states what it did not check. Deeply nested YAML took the process down with SIGSEGV before any handler ran, because libyaml's parser recurses in C. Depth is bounded with the scanner, which tokenises iteratively and costs less than the load it guards.
An omitted descent_power_w fell back to cruise power, which bills one of the most expensive phases at the cheapest number in the profile for an aircraft that comes down on its lift rotors. No shipped profile declares the field, so this was the path that ran: worst-case RTH energy was understated by 37 %. usable_capacity_curve was read only at soc=1.0, and the ordering rule made that the curve's maximum, so every natural sag curve was an exact no-op. It is a scalar usable_capacity_fraction in vehicle.v6. Air temperature never reached the density model, sustained turns and fixed-wing orbits were billed at level cruise power despite holding a bank, and the reserve percentage was a share of deliverable capacity while its threshold was a share of nameplate, so a report could print a landing reserve above the bar it had just failed.
A fixed-wing orbit contributed one population sample, at its centre, while the report claimed a 160 m corridor. On a worked case that filed iGRC 3 where the real ground track gives 6. The orbit is walked now, and a missing turn radius fails closed. The 1:1 ground risk buffer was validated against terrain under the route centreline, so ground falling away inside the buffer went unseen; the sampler takes a corridor width. effective_resolution_m was inert metadata, and the shipped grid filed 100 m for cells 1113 m by 685 m. over_urban_area and airspace_class were operator booleans the run never questioned, though the fetched data can contradict them. The containment column is the leftmost meeting the required robustness, as in the spec's worked example. The filed artifact now also says airspace currency was never assessed, which its banner previously implied by naming only two omissions.
The projection depends only on the segment but was constructed inside the obstacle loop, so a 50-obstacle mission spent 92 % of its runtime rebuilding pyproj transformers it already had. Obstacle evidence is mandatory for a GO, so this was the only path to an operational verdict: 100 obstacles took 26.8 s and now take 0.7 s, with unchanged results. ListObstacleProvider also never set provider_id, because slots plus a custom __init__ left the declared default off the instance, so provenance recorded the class name instead of static_list.
drift_var_per_s was a deg² quantity used as deg²/s, so process noise came from the measurement parameter. A receiver with availability 0 and a 50 m accuracy figure produced 1453 m of unaided drift where 2.5 m gave 73 m, which is a category error rather than a tuning choice. Affects the stochastic diagnostic only; no shipped profile changes.
--source openaip never worked: api.openaip.net does not resolve, and the host alone was not the problem. The Core API returns an items envelope rather than features, its records are flat, and icaoClass and type are integer enums the string comparison could never match, so a corrected request would still have written no zones. Pagination was unhandled. openAIP airspace was attributed to OpenStreetMap under ODbL. It is CC BY-NC-SA, and Open-Meteo's free tier is likewise non-commercial, which NOTICE.md never said. Both now state the restriction and can be pointed at a paid endpoint. fetch_wind sampled one point and wrote a grid 0.22 m wide, which the coverage check rejects; it takes a bounding box now, stepped to match the model's own resolution. Fetched assets carry fetched_at, source and licence, obstacle heights can follow a terrain grid instead of one scalar base altitude, fetch_all only advertises assets it wrote, and the Overpass endpoint is no longer hard-coded. SECURITY.md pointed at a private reporting channel that was switched off; it is enabled, and malformed-input crashes are in scope.
Nothing type-checked the package and no py.typed shipped, so downstream importers got none of the annotations the code already carried. The gate is a ratchet: everything is checked by default and the modules still carrying findings are listed as exclusions, so a new module is covered the moment it exists. All six runtime dependencies were unbounded, so a future pydantic 3 or shapely 3 would install cleanly into a fresh pip install and break at runtime. They are capped at the next major, matching the reasoning already applied to the pinned ruff rule set.
The tutorial's GO block quoted a reserve figure that had moved twice and nothing pinned it, which is why the drift landed there. Both fetch commands in data/README.md used flags that do not exist and exited 2. sitl.md told Docker users to substitute docker for podman when sitl/ ships only a Containerfile. CONTRIBUTING pointed at a main module that has never existed. The ticket index disagreed with itself three ways and left six tickets unlisted. Extends the doc test harness to re-run and pin getting-started, which carries the numbers and was the only major page no test covered.
A calibration fitting cruise power alone vouched for coefficients it never fitted: with the reference mission, real traces and hover/descent edited to 1 W, the run printed GO with no warnings and 9.93 Wh against an honest 24.92 Wh. The estimator now derives what each mission bills against and names both the unfitted coefficient and the value the vehicle declares for it, so an acknowledged waiver stays reviewable. batch and scenario dropped the non-waivable floor under --engineering-only, so a mission estimate refused at exit 10 came back feasible at exit 0. The wind grid's below-floor clamp was unbounded, letting a 3000-5000 m forecast certify a route at 12-132 m. Geofence bounds still accepted the finite sentinels that disarm a zone the way NaN did. The output envelopes had gained a field, new enum members and a changed Literal while their version strings stood still, so a consumer pinned on the published version could not parse them.
The SORA footprint check measured a fixed-wing orbit at its centre, so terrain under the flown ring never reached the buffer filed to an authority: a gorge under the ring moved the reported maximum AGL from 132 m to 432 m. Return-to-home and divert paths flew Dubins arcs at the turn radius but paid flat cruise power while the mission's own arcs paid the bank load factor, leaving the contingency budget optimistic. ICAO Class A and B never became keep-out geofences, so an uncoordinated route through Class B got no forbidden zone and the file looked complete. Fetched geofences also carried openAIP activation windows in the fields that drop a zone from conflict checks, so a lapsed window silently stopped a restricted area blocking the route. Landing-zone states printed one reserve against deliverable capacity beside a threshold against nameplate. The descent fallback claimed hover power bounds descent from above without enforcing it, the bank factor ran unbounded on a small declared turn radius, and GeoJSON assets skipped the nesting guard every other input goes through.
The nesting guard reached the estimator's loaders and nothing else. A QGroundControl .plan comes from a third party and a legacy mission comes from wherever the operator kept it, yet both parsed raw, so a deeply nested file surfaced as an internal error rather than the bad input it is. A mission.v6 file accepting GEOFENCE_ZERO_ZONES was valid against the released tool and now has no field-for-field upgrade. Stripping the entry would delete the operator's written record that they reviewed and accepted that state, so the migration refuses it and names the codes and the remedy. Failing through mission.v7 validation instead reported an exceedance of a vehicle limit the file never exceeded; each non-waivable code now carries its own reason.
The mypy step was the only one running unpinned, so it resolved its own tool and stubs at job time and failed on missing `requests` stubs that a developer with the scripts extra installed never sees. mypy and types-PyYAML move into the dev group, the step runs `--locked` like every other, and `requests` joins the stubless third-party libraries already listed — it is only imported from modules the gate excludes. The Overpass query test guarded nothing, so it raised on the module's own `requests` check instead of exercising the query. Every other test in that file patches the attribute for exactly this reason. Both failures needed CI's own dependency set to reproduce: a local venv carrying the scripts extra hides them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Test Plan
uv run pytestpassesuv run ruff check .passesContract Impact
CONTRIBUTING.md(Public contracts)