44 workflow_dispatch :
55 inputs :
66 feeds_path :
7- description : " RSS feed config CSV path."
7+ description : " Must remain the canonical feed configuration path."
88 required : false
99 default : " config/free_rss_feeds.csv"
1010 type : string
1111 aliases_path :
12- description : " Symbol alias CSV path."
12+ description : " Must remain the canonical alias configuration path."
1313 required : false
1414 default : " config/core_us_equity_aliases.csv"
1515 type : string
1616 watchlist_path :
17- description : " Watchlist CSV path."
17+ description : " Must remain the canonical watchlist path."
1818 required : false
1919 default : " data/live/political_watchlist.csv"
2020 type : string
2828 required : false
2929 default : " false"
3030 type : choice
31- options :
32- - " false"
33- - " true"
31+ options : ["false", "true"]
3432 schedule :
3533 - cron : " 15 12 * * 6"
3634
@@ -42,74 +40,112 @@ concurrency:
4240 cancel-in-progress : false
4341
4442jobs :
43+ validate-workflow-boundary :
44+ if : >-
45+ github.repository == 'QuantStrategyLab/PoliticalEventTrackingResearch' &&
46+ github.ref == 'refs/heads/main' &&
47+ github.workflow_ref == 'QuantStrategyLab/PoliticalEventTrackingResearch/.github/workflows/rss_source_pipeline.yml@refs/heads/main'
48+ runs-on : ubuntu-latest
49+ steps :
50+ - name : Validate canonical workflow inputs
51+ env :
52+ EVENT_NAME : ${{ github.event_name }}
53+ FEEDS_PATH : ${{ github.event.inputs.feeds_path || 'config/free_rss_feeds.csv' }}
54+ ALIASES_PATH : ${{ github.event.inputs.aliases_path || 'config/core_us_equity_aliases.csv' }}
55+ WATCHLIST_PATH : ${{ github.event.inputs.watchlist_path || 'data/live/political_watchlist.csv' }}
56+ run : |
57+ set -euo pipefail
58+ python3 - <<'PY'
59+ import os
60+ expected = (
61+ "config/free_rss_feeds.csv",
62+ "config/core_us_equity_aliases.csv",
63+ "data/live/political_watchlist.csv",
64+ )
65+ actual = tuple(os.environ[key] for key in ("FEEDS_PATH", "ALIASES_PATH", "WATCHLIST_PATH"))
66+ if actual != expected:
67+ raise SystemExit("workflow_input_path_invalid")
68+ PY
69+
4570 build-rss-source-events :
71+ needs : validate-workflow-boundary
72+ if : needs.validate-workflow-boundary.result == 'success'
4673 runs-on : ubuntu-latest
4774 timeout-minutes : 30
4875 steps :
4976 - uses : actions/checkout@v6
77+ with :
78+ repository : QuantStrategyLab/PoliticalEventTrackingResearch
79+ ref : ${{ github.sha }}
80+ persist-credentials : false
81+ - name : Verify reviewed main checkout
82+ env :
83+ EXPECTED_SHA : ${{ github.sha }}
84+ run : test "$(git rev-parse HEAD)" = "${EXPECTED_SHA}"
5085 - uses : actions/setup-python@v6
5186 with :
5287 python-version : " 3.11"
5388 - name : Install package
5489 run : python -m pip install -e .
5590 - name : Fetch RSS sources and extract mentions
5691 env :
57- FEEDS_PATH : ${{ github.event.inputs.feeds_path || 'config/free_rss_feeds.csv' }}
58- ALIASES_PATH : ${{ github.event.inputs.aliases_path || 'config/core_us_equity_aliases.csv' }}
59- WATCHLIST_PATH : ${{ github.event.inputs.watchlist_path || 'data/live/political_watchlist.csv' }}
6092 MAX_ITEMS_PER_FEED : ${{ github.event.inputs.max_items_per_feed || '50' }}
6193 run : |
6294 set -euo pipefail
6395 mkdir -p data/output/rss_source_pipeline
96+ fetch_exit=0
6497 python scripts/fetch_rss_sources.py \
65- --feeds "${FEEDS_PATH}" \
98+ --feeds config/free_rss_feeds.csv \
6699 --output data/output/rss_source_pipeline/source_items.csv \
67100 --max-items-per-feed "${MAX_ITEMS_PER_FEED}" \
68101 --continue-on-feed-error \
69- --status-output data/output/rss_source_pipeline/source_fetch_status.json
70- python scripts/extract_source_mentions.py \
71- --raw-items data/output/rss_source_pipeline/source_items.csv \
72- --aliases "${ALIASES_PATH}" \
73- --output data/output/rss_source_pipeline/source_events.csv
74- python scripts/build_tracker.py \
75- --watchlist "${WATCHLIST_PATH}" \
76- --events data/output/rss_source_pipeline/source_events.csv \
77- --output data/output/rss_source_pipeline/source_tracker.csv
102+ --status-output data/output/rss_source_pipeline/source_fetch_status.json || fetch_exit=$?
103+ if [ -f data/output/rss_source_pipeline/source_items.csv ]; then
104+ python scripts/extract_source_mentions.py \
105+ --raw-items data/output/rss_source_pipeline/source_items.csv \
106+ --aliases config/core_us_equity_aliases.csv \
107+ --output data/output/rss_source_pipeline/source_events.csv
108+ python scripts/build_tracker.py \
109+ --watchlist data/live/political_watchlist.csv \
110+ --events data/output/rss_source_pipeline/source_events.csv \
111+ --output data/output/rss_source_pipeline/source_tracker.csv
112+ fi
113+ printf 'fetch_exit=%s\n' "${fetch_exit}"
114+ - name : Upload RSS source artifact
115+ uses : actions/upload-artifact@v7
116+ with :
117+ name : rss-source-pipeline
118+ path : data/output/rss_source_pipeline/
119+ if-no-files-found : error
120+ - name : Validate feed completeness
121+ run : python scripts/validate_fetch_status.py --status data/output/rss_source_pipeline/source_fetch_status.json
78122 - name : Publish live CSV outputs to repository
79123 env :
80124 COMMIT_OUTPUTS : ${{ github.event_name == 'schedule' && 'true' || github.event.inputs.commit_outputs || 'false' }}
125+ GITHUB_TOKEN : ${{ github.token }}
126+ EXPECTED_SHA : ${{ github.sha }}
81127 run : |
82128 set -euo pipefail
83- if [ "${COMMIT_OUTPUTS}" != "true" ]; then
84- echo "Live output commit disabled."
85- exit 0
86- fi
129+ if [ "${COMMIT_OUTPUTS}" != "true" ]; then echo "Live output commit disabled."; exit 0; fi
130+ test "$(git rev-parse HEAD)" = "${EXPECTED_SHA}"
87131 mkdir -p data/live
88132 cp data/output/rss_source_pipeline/source_items.csv data/live/source_items.csv
89133 cp data/output/rss_source_pipeline/source_events.csv data/live/source_events.csv
90134 cp data/output/rss_source_pipeline/source_events.csv data/live/political_events.csv
91135 cp data/output/rss_source_pipeline/source_tracker.csv data/live/source_tracker.csv
92136 cp data/output/rss_source_pipeline/source_fetch_status.json data/live/source_fetch_status.json
93- python scripts/write_live_manifest.py \
94- --base-dir . \
95- --output data/live/source_manifest.json \
96- data/live/source_fetch_status.json \
97- data/live/source_items.csv \
98- data/live/source_events.csv \
99- data/live/political_events.csv \
100- data/live/source_tracker.csv
137+ python scripts/write_live_manifest.py --base-dir . --output data/live/source_manifest.json data/live/source_fetch_status.json data/live/source_items.csv data/live/source_events.csv data/live/political_events.csv data/live/source_tracker.csv
101138 git config user.name "github-actions[bot]"
102139 git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
103140 git add data/live/source_items.csv data/live/source_events.csv data/live/political_events.csv data/live/source_tracker.csv data/live/source_fetch_status.json data/live/source_manifest.json
104141 if git diff --cached --quiet; then
105142 echo "No live RSS output changes to commit."
106143 else
107144 git commit -m "Update live RSS source events [skip ci]"
108- git push
145+ git config --local http.https://github.com/.extraheader "AUTHORIZATION: bearer ${GITHUB_TOKEN}"
146+ cleanup_git_auth() { git config --local --unset-all http.https://github.com/.extraheader >/dev/null 2>&1 || true; }
147+ trap cleanup_git_auth EXIT
148+ git push origin HEAD:refs/heads/main
149+ cleanup_git_auth
150+ trap - EXIT
109151 fi
110- - name : Upload RSS source artifact
111- uses : actions/upload-artifact@v7
112- with :
113- name : rss-source-pipeline
114- path : data/output/rss_source_pipeline/
115- if-no-files-found : error
0 commit comments