- record only what the repo cannot reveal on inspection: intent, traps, prohibitions - cover injection rules, invocation modes, silent-corruption couplings and dead paths - flag warts that need an owner decision rather than a silent fix Generated-by: Claude Opus 5 <noreply@anthropic.com>
6.5 KiB
Never interpolate ${{ github.event.* }} into a run: block
Actions substitutes ${{ }} as raw text before the shell parses it, and issue bodies here are
public and unauthenticated. Pass untrusted values through env: and quote them.
A quoted heredoc delimiter does not save you: the body can contain the delimiter, close the
heredoc early, and execute every line after it. validate-community-submission.yaml had that shape.
Never fix this class of bug by escaping, sanitizing, or renaming the delimiter — move it to env:.
Run everything from the repo root
src/create_daily_faa_release.py must be invoked as a script (python src/create_daily_faa_release.py).
It uses bare sibling imports, so -m src.create_daily_faa_release raises ModuleNotFoundError.
Everything under src/adsb/ and src/contributions/ is the opposite — python -m, package-relative.
Output paths are CWD-relative.
Verification
There is no test framework, linter, or packaging config. Do not add one unprompted, and do not treat "nothing broke" as verification.
The ADS-B path has no cheap end-to-end check — one day of input is tens of GB. Exercise
compress_multi_icao_df / compress_df_polars directly against a small hand-built Polars frame.
Never gh workflow run to test a change. Every dispatch pulls tens of GB and fans out over date
matrices. Reason about the YAML statically.
Never commit generated data. The product is a GitHub Release; jobs pass state as artifacts.
ADS-B invariants
FINAL_COLUMN_ORDER(compress_adsb_to_aircraft_data.py) is the only definition of the released column contract.pl.concatmatches by position after.select(), andget_latest_release.get_latest_aircraft_adsb_csv_dfparses released CSVs against the same order — so a forked copy corrupts the release with no error anywhere.load_parquet_part()deleting its source parquet is deliberate: the raw part is many GB and the runner would otherwise exhaust disk. Do not defer the delete to make reruns easier; that raises peak disk by the size of the part.- A released row means "most informative observation for this ICAO on this UTC day" — non-empty fields not a subset of another row's, tie-broken by signature frequency. It is not a registry record.
- HTTP 404 is terminal in the release fetch. Restoring the retry makes the Dec-31 next-year-repo probe stall ~45 minutes on a repo that does not exist yet.
Fork and upstream
src/get_latest_release.py pins REPO = "PlaneQuery/openairframes" on purpose: this fork reads
upstream's releases wherever it runs. Do not repoint it at github.repository without being asked.
Upstream develops on develop and PRs into main. The daily release deletes the existing release
and tag before recreating them.
Community submissions are automation-owned
Merging to community/** or schemas/** force-pushes every open community-labeled PR branch back
onto main. Anything you hand-edit on such a branch is destroyed on the next merge.
- Never hand-author files in
community/— the filename encodessha256(content)[:8], so an edit orphans the hash and duplicates on re-approval. - Never invent or copy a
contributor_uuid; it is derived from the GitHub user id. - Do not reintroduce a hardcoded
"main"orv1filename. Both are resolved at runtime now.
A tag's JSON type is fixed by its first-ever submission and enforced forever — emergent from
build_tag_type_registry + validate_submission, and written nowhere in the schema. Retyping or
renaming an existing tag breaks every future contributor, not just the current one.
Dropping a community_submission.v2.schema.json into schemas/ promotes it atomically across every
reader and writer. That is a one-way door for contributors — only on explicit request.
Conventions
- Empty string, not null, everywhere in released frames.
- Reuse
derive_from_faa_master_txt.normalize()foropenairframes_id; never re-derive the format. - Python
3.14for FAA/community/vendor jobs,3.12for ADS-B jobs (pyarrow pin + multiprocessing). Deliberate. Match the surrounding job; do not unify.
References with no target — do not chase as regressions
| Reference | Missing |
|---|---|
process-historical-faa.yaml |
src/get_historical_faa.py, scripts/concat_csvs.py |
af-klm-fleet/package.json → npm run validate |
af-klm-fleet/scripts/validate.js |
process-historical-faa.yaml is dead, not stale — it also uses the disabled ::set-output.
Repair-vs-delete is the owner's call; leave it alone unprompted.
af-klm-fleet/ and community-routes/ are unwired
Nothing in CI touches either, and nothing consumes community-routes/. af-klm-fleet/ is a vendored
project by a different author with its own license — its aircraft model is unrelated to
schemas/community_submission.*, so do not merge the two. Its README.md is generated by
generate-readme.js; hand edits are overwritten.
Warts left standing — flag, do not silently fix
NUMBER_PARTSis restated by the matrix and four hand-written upload steps inadsb-to-aircraft-for-day.yaml; changing the constant alone silently drops data. YAML cannot loop upload steps and one merged artifact would force every map job to download all parts, so any real fix is a restructure.MAX_WORKERS = OS_CPU_COUNT if OS_CPU_COUNT > 4 else 1collapses to a single worker on a ≤4-core runner, shrinkingfiles_per_batchwith it. Possibly intentional memory control — do not raise it without measuring peak RSS on the target runner.update-community-prs.yamlrunsregenerate_pr_schema || truethen force-pushes, so a regeneration failure ships anyway. Making it fatal leaves PRs un-rebased instead — a judgment call.approve_submission.pywraps its schema update in a bareexcept Exception, so a submission can merge without its new tags reaching the schema.
Workflow authoring
The user's global GitHub Actions rules apply. Existing workflows violate most of them. Do not bulk-remediate — bring only the file you were asked to touch up to standard, and surface the rest in chat.
External sources
registry.faa.gov and ADS-B Exchange are required — the release fails without them. Mictronics is
tolerated: it retries, then the job continues without it. adsb.lol may simply not have published a
given day, in which case the previous CSV is re-released rather than failing.
FAA refreshes at 05:30 UTC; the release cron fires at 06:00 UTC. That 30-minute margin is the reason for the schedule.