Skip to content

fix(xbrl): an amended annual report is still an annual report; cash f… #887

fix(xbrl): an amended annual report is still an annual report; cash f…

fix(xbrl): an amended annual report is still an annual report; cash f… #887

# Regression Tests Workflow
# Runs regression tests on-demand or on schedule to catch breaking changes
name: Regression Tests
on:
# Manual trigger
workflow_dispatch:
# Run weekly on Sundays
schedule:
- cron: '0 8 * * 0'
# ADVISORY pre-merge run. This lane MUST NEVER become a required check.
#
# The 647 network-marked regression tests gate nothing today: they run only
# after merge, so a break is invisible on the pull request that caused it.
# That is not hypothetical — three of four consecutive post-merge runs failed
# the week of 2026-08-08 with 16 failures across three files, all network-
# marked, one of them genuine content loss in `Filing.text()` that sat on main
# for three commits (bead edgartools-07lk.24, finding 1).
#
# Advisory, for two independent reasons, and both have to hold:
# 1. It is paths-filtered. A docs-only pull request produces no run at all,
# and a required check that never reports blocks the pull request forever
# (the deadlock documented at the top of python-hatch-workflow.yml).
# 2. It is ~26 minutes against the live SEC, so it is far too slow and too
# network-dependent to sit in front of a merge.
# The required contexts stay exactly the two named in python-hatch-workflow.yml.
# Making this one required is the fastest way to deadlock the repository.
pull_request:
branches: [ "main" ]
paths:
- 'tests/issues/regression/**'
- 'edgar/**'
- '.github/workflows/regression-tests.yml'
- 'tests/conftest.py'
- 'tests/_offline_filings.py'
- 'tests/_offline_harness.py'
- 'tests/_vcr_safety.py'
- 'pyproject.toml'
# Also run on main branch pushes that touch the library.
#
# This used to name three packages — edgar/xbrl, edgar/entity and the
# regression tree itself — which left every other package uncovered. A change
# under edgar/documents that added no regression file ran the suite in no job
# at all, and that is where the section-extraction defect cluster lives
# (bead edgartools-07lk.21). `edgar/**` closes the hole as a class instead of
# one package at a time; docs- and script-only merges still skip it.
#
# The four test-harness files are here because they decide what this lane
# RUNS, not just what it runs against. tests/conftest.py assigns every
# regression test its fast/network marker and fails collection on an
# unclassified file; _offline_filings.py backs the tests that resolve an
# accession without the quarterly index; _offline_harness.py is the offline
# measurement; _vcr_safety.py installs the safe YAML loader the cassette gate
# exists to protect. A pull request can break this suite by touching only
# those and never trigger the lane — PR #1013 was exactly that shape.
# pyproject.toml carries the marker registry and pytest options
# (xfail_strict among them), which change outcomes suite-wide.
push:
branches: [ "main" ]
paths:
- 'tests/issues/regression/**'
- 'edgar/**'
- '.github/workflows/regression-tests.yml'
- 'tests/conftest.py'
- 'tests/_offline_filings.py'
- 'tests/_offline_harness.py'
- 'tests/_vcr_safety.py'
- 'pyproject.toml'
# Cancel superseded regression runs on the same ref.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# See python-hatch-workflow.yml: cassettes are loaded with PyYAML's unsafe
# loader, so this has to pass before anything runs pytest.
#
# The name carries the "(regression)" suffix on purpose. `main` requires the
# context "Cassette safety gate", produced by the identically-named job in
# python-hatch-workflow.yml. Once this workflow runs on pull requests too, an
# unsuffixed name here would put two check runs with that one required name on
# every pull request touching edgar/**, and branch protection matches contexts
# by name. Either this advisory job's red blocks a merge, or its green
# satisfies the requirement the real gate was supposed to answer — and which
# of the two you get depends on reporting order. Keep the names distinct.
cassette-gate:
name: Cassette safety gate (regression)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python 3.13
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.13'
- name: Install PyYAML
run: python -m pip install --upgrade pip PyYAML
- name: Scan cassettes for unsafe YAML tags
run: python scripts/check_cassettes.py
regression:
needs: cassette-gate
runs-on: ubuntu-latest
env:
EDGAR_LOCAL_DATA_DIR: ${{ github.workspace }}/.edgar-data
strategy:
fail-fast: false
matrix:
# Single version: regression tests verify data correctness against real
# filings, not Python compatibility (the fast job covers 3.10 + 3.13).
python-version: ["3.12"]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
# Persist the SEC HTTP cache — regression tests fetch real filings, and
# /Archives/edgar/data is cached forever (see CACHE_RULES), so a warm cache
# serves them from disk instead of re-downloading.
#
# Restore and save are split so pull requests can READ the cache without
# WRITING one. This entry is 857 MB, and the repository was already at
# 10.7 GB against a 10 GB ceiling when the pull-request trigger was added,
# so it is evicting LRU today. A save on every pull-request run would push
# main's warm entry out and leave the lane re-downloading a filing set it
# already had — slower, and rude to the SEC. Pull-request runs restore from
# whatever main last saved and throw their own copy away.
- name: Restore SEC HTTP cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ${{ github.workspace }}/.edgar-data/_tcache
key: edgar-tcache-regression-${{ github.run_id }}
restore-keys: |
edgar-tcache-regression-
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -e ".[ai]"
python -m pip install pytest pytest-cov pytest-env pytest-xdist pytest-asyncio pytest-retry pytest-mock pytest-vcr "vcrpy<8.2" freezegun==1.5.1 filelock tqdm responses
- name: Run regression tests
run: |
pytest --cov --cov-report=xml -m regression --enable-cache
# `always()`: a run that failed still warmed the cache, and the next one
# should not pay for those downloads again.
- name: Save SEC HTTP cache
if: always() && github.event_name != 'pull_request'
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ${{ github.workspace }}/.edgar-data/_tcache
key: edgar-tcache-regression-${{ github.run_id }}
# Not on pull requests. Codecov posts its own commit statuses, and a partial
# `regression`-flag upload from an advisory lane would put a red check on a
# pull request that this workflow is explicitly not allowed to influence.
- name: Upload coverage reports
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
if: matrix.python-version == '3.12' && github.event_name != 'pull_request'
with:
files: coverage.xml
fail_ci_if_error: false
flags: regression
token: ${{ secrets.CODECOV_TOKEN }}
# A red `main` has to arrive somewhere a person looks.
#
# This lane runs post-merge, so nothing blocks on it and its result lands in
# the Actions tab and nowhere else. On 2026-08-08 it went red at 13:00 and
# three more pull requests merged through it before anyone noticed — the
# failures were visible the whole time and no one was looking (edgartools-hwdp).
#
# One issue, reopened and commented rather than duplicated, so a week of
# nightly failures is one thread and not seven. It closes itself when the lane
# goes green again, which makes the issue list the answer to "is main ok?".
# `needs` covers both jobs on purpose. A failed cassette gate skips the
# regression job rather than failing it, so keying only on that job's result
# would stay silent for the one failure that stops the suite running at all.
# Neither a cancelled run (concurrency supersedes one on every push) nor a
# skipped one is treated as red.
#
# The `github.event_name != 'pull_request'` guard became load-bearing when the
# advisory pre-merge trigger was added: "main is red" is a claim about main,
# and a pull request that breaks a regression test must not file it. Its
# `issues: write` would not survive a fork pull request anyway.
report:
name: Report red main
needs: [cassette-gate, regression]
if: always() && github.event_name != 'pull_request'
runs-on: ubuntu-latest
permissions:
issues: write
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
SHA: ${{ github.sha }}
LABEL: red-main
TITLE: "Regression Tests are failing on main"
steps:
- name: Ensure the tracking label exists
run: |
gh label create "$LABEL" --repo "$REPO" --color b60205 \
--description "Post-merge regression lane is red" 2>/dev/null || true
- name: Open or update the tracking issue
if: needs.regression.result == 'failure' || needs['cassette-gate'].result == 'failure'
run: |
number=$(gh issue list --repo "$REPO" --label "$LABEL" --state all \
--limit 1 --json number --jq '.[0].number // empty')
body=$(printf '%s\n\n- commit: %s\n- run: %s\n' \
"The post-merge regression lane failed on \`main\`." "$SHA" "$RUN_URL")
if [ -z "$number" ]; then
gh issue create --repo "$REPO" --label "$LABEL" \
--title "$TITLE" --body "$body"
else
gh issue reopen --repo "$REPO" "$number" 2>/dev/null || true
gh issue comment --repo "$REPO" "$number" --body "$body"
fi
- name: Close the tracking issue when main is green again
if: needs.regression.result == 'success' && needs['cassette-gate'].result == 'success'
run: |
number=$(gh issue list --repo "$REPO" --label "$LABEL" --state open \
--limit 1 --json number --jq '.[0].number // empty')
if [ -n "$number" ]; then
gh issue close --repo "$REPO" "$number" \
--comment "Green again at $SHA — $RUN_URL"
fi