Repository navigation
141 lines (128 loc) · 6.06 KB
/
Copy pathrecord.yml
File metadata and controls
141 lines (128 loc) · 6.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# The Congressional Record's daily loop.
#
# Separate from update.yml on purpose. The bills heartbeat is this project's
# liveness signal, and folding the Record into that job would let a Record
# fetch error take the signal down with it. Two schedules, two state files,
# one STATUS.md -- whichever job runs last renders the page from both, so a
# Record loop that stops shows up as a date that no longer moves while the
# bills loop keeps publishing the page. Neither loop can report its own death;
# that is why the other one renders it.
#
# The Record was 8 days stale when this was written, because nothing scheduled
# built it: `update` handles bills and reports US Code release points, and
# never touched the Record at all.
#
# This job is append-only by construction. `update-record` builds issue days a
# branch does not hold yet and never passes --rebuild, so every push is a
# fast-forward. That matters more here than for bills: the Record is one
# cumulative branch, so rewriting a day in the middle rewrites everything after
# it. govinfo restamps already-published days in bulk -- nine of them on
# 2026-08-12, with identical contents -- and a modification-driven job would
# have force-pushed the whole history to produce the same trees.
name: record
on:
schedule:
# 07:00 UTC daily, two hours after update.yml so the two never contend for
# the same push credential, and late enough that the previous day's issue
# has settled on govinfo.
- cron: "0 7 * * *"
workflow_dispatch:
inputs:
congress:
description: "Override the Congress, e.g. 119. Blank for the sitting one"
required: false
type: string
permissions:
contents: write
concurrency:
# Its own group: this must never queue behind or cancel the bills loop.
group: record
cancel-in-progress: false
jobs:
record:
runs-on: ubuntu-24.04
timeout-minutes: 90
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install uv
# Pinned to a commit, matching the other three workflows: a tag can be
# moved, and astral-sh stopped publishing floating major tags after v7,
# so `@v9` does not resolve. Bump all four together and deliberately;
# tests/test_toolchain.py fails if they drift. `version` pins the uv the
# action installs, which the commit does not; see that test for the
# morning that cost.
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.13.0"
enable-cache: true
- name: Install
run: uv sync --locked
- name: Test
# Cheap, and the alternative is a defective build writing into a public
# repository before anyone notices.
run: uv run pytest -q
- name: Build and publish the Record
id: record
# Failure is handled below: the heartbeat has to be written and pushed
# first, or a run that failed leaves no public trace that it ran at all.
continue-on-error: true
env:
GOVINFO_API_KEY: ${{ secrets.GOVINFO_API_KEY }}
GITHUB_TOKEN: ${{ secrets.DATA_REPO_TOKEN }}
# Through the environment, never interpolated into the command. A
# workflow_dispatch input is free text, and `${{ }}` inside `run:` is
# substituted before the shell sees it, so a value containing shell
# metacharacters would execute rather than be parsed as a number.
CONGRESS: ${{ inputs.congress }}
run: |
if [ -n "$CONGRESS" ]; then
uv run uscongress update-record --publish --congress "$CONGRESS"
else
uv run uscongress update-record --publish
fi
- name: Publish the heartbeat
# Runs even when the build failed. A failed run is exactly the one whose
# outcome most needs to be visible.
if: always()
run: |
git config user.name "us-congress-pipeline"
git config user.email "pipeline@junxit.invalid"
# Written in shell, by a step that always runs, because the failure
# this guards against is the Python job never starting: `uv sync` or
# `pytest` failing aborts before the work step, so nothing is written,
# nothing is staged and nothing is committed. Daily red runs with zero
# repository activity, and GitHub disables a scheduled workflow after
# 60 days of that. Its own file, not a shared one, so the two loops
# never collide on the same line during the rebase-retry below.
printf 'run %s\nstarted %s\nwork step: %s\n' \
"${{ github.run_id }}" \
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
"${{ steps.record.outcome || 'did not run' }}" \
> state/ci-record.txt
git add state/ci-record.txt || true
# Only the watermark otherwise. STATUS.md is rendered from it by the
# bills loop at 05:00, which is what makes a stopped Record loop
# visible on a page something else is still writing -- see
# recordloop's docstring.
git add state/record.json || true
if git diff --cached --quiet; then
echo "heartbeat unchanged"
exit 0
fi
git commit -m "chore: update Record heartbeat
Written by the Congressional Record job. The date in STATUS.md is the
signal: it goes stale on its own if this stops running, which is the
failure mode that raises no error."
# Retry once against anything that landed while this ran -- the bills
# loop commits to the same branch, and losing this push would leave
# the heartbeat reading stale when the job in fact worked.
git push || {
git pull --rebase --autostash
git push
}
- name: Fail if the build did not succeed
if: steps.record.outcome == 'failure'
run: |
echo "The Record build failed; nothing was published and the"
echo "heartbeat records the failure. See the build step for detail."
exit 1