Skip to content

Commit 0c31a79

Browse files
Update excel_inbox.yaml
1 parent c9713dd commit 0c31a79

1 file changed

Lines changed: 103 additions & 28 deletions

File tree

.github/workflows/excel_inbox.yaml

Lines changed: 103 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,89 @@
11
# .github/workflows/excel_inbox.yaml
22
#
3-
# Excel Inbox — PR validation
4-
# ============================
5-
# Triggered when a pull request adds or modifies a file inside inbox/.
3+
# Excel Inbox — PR validation and cleanup
4+
# =========================================
5+
# Triggered when a pull request to main is created or updated and touches
6+
# the inbox/ folder.
67
#
7-
# This workflow only validates — it never pushes anything back to the branch.
8-
# That keeps it safe for both same-repo and fork PRs.
8+
# Uses pull_request_target so the workflow runs in the base repo's context
9+
# and can push back to the PR branch — including forks.
10+
#
11+
# Security note: this is safe because the workflow never executes any
12+
# contributor-controlled code. It only opens the xlsx file with openpyxl
13+
# and runs scripts checked out from main (not from the PR).
14+
# See: https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/
915
#
1016
# Contributor flow:
1117
# 1. Download docs/assets/coremeta4cat_vocabulary.xlsx.
12-
# 2. Edit it locally.
13-
# 3. Open a PR placing the file at inbox/coremeta4cat_vocabulary.xlsx.
14-
# 4. This workflow validates the file and posts a comment with the result.
15-
# 5. On merge to main, the update_excel workflow regenerates
16-
# docs/assets/coremeta4cat_vocabulary.xlsx from the schema automatically.
18+
# 2. Edit it, open a PR with the file at inbox/coremeta4cat_vocabulary.xlsx.
19+
# 3. This workflow validates the file, posts a comment with the result,
20+
# and on success removes the inbox file from the PR branch so that
21+
# docs/assets/ is cleanly regenerated from the schema on merge.
22+
# 4. On merge to main, update_excel.yaml regenerates
23+
# docs/assets/coremeta4cat_vocabulary.xlsx from the schema.
1724
#
18-
# Note: the inbox file is NOT promoted to docs/assets/ here.
19-
# The ground truth is always the schema; the Excel is derived output.
20-
# If the contributor's edits require schema changes, those should be made to
21-
# the YAML source files and the workbook will be regenerated on merge.
25+
# Note: the Excel is derived output — the schema YAML is the ground truth.
26+
# If the contributor's edits require schema changes, those should be made
27+
# to the YAML source files and included in the same PR.
2228
---
2329
name: Excel inbox validation
2430

2531
on: # yamllint disable-line rule:truthy
26-
pull_request:
32+
pull_request_target:
33+
branches:
34+
- main
35+
types: [opened, reopened, synchronize]
2736
paths:
2837
- "inbox/**"
38+
workflow_dispatch:
2939

3040
env:
3141
FORCE_COLOR: "1"
3242
INBOX_FILE: "inbox/coremeta4cat_vocabulary.xlsx"
3343

34-
permissions:
35-
pull-requests: write # needed to post the summary comment
44+
concurrency:
45+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
46+
cancel-in-progress: true
47+
48+
permissions: {}
3649

3750
jobs:
3851
validate-inbox:
52+
name: Validate inbox Excel and clean up
53+
if: ${{ !github.event.pull_request.merged }}
3954
runs-on: ubuntu-latest
55+
timeout-minutes: 10
56+
57+
permissions:
58+
contents: write # to commit cleanup back to the PR branch
59+
pull-requests: write # to post the summary comment
4060

4161
steps:
4262

43-
- name: Check out repository
63+
- name: Check out PR branch (fork-safe)
64+
uses: actions/checkout@v6.0.3
65+
with:
66+
# Check out the fork's branch so we can push back to it.
67+
# Using pull_request_target + explicit head repo is the standard
68+
# pattern for safe write-back to fork PRs.
69+
repository: ${{ github.event.pull_request.head.repo.full_name }}
70+
ref: ${{ github.event.pull_request.head.ref }}
71+
fetch-depth: 0
72+
persist-credentials: true
73+
74+
- name: Check out main branch scripts into _main_branch/
75+
# Always run our own scripts from main, never from the PR.
76+
# This is the key security boundary for pull_request_target.
4477
uses: actions/checkout@v6.0.3
45-
# No ref override — uses the PR merge commit, works for forks too.
4678
with:
79+
ref: main
80+
path: _main_branch
81+
sparse-checkout: |
82+
scripts/
83+
src/
84+
pyproject.toml
85+
uv.lock
86+
fetch-depth: 1
4787
persist-credentials: false
4888

4989
- name: Install uv
@@ -53,8 +93,9 @@ jobs:
5393
enable-cache: true
5494
cache-dependency-glob: "uv.lock"
5595

56-
- name: Install project
96+
- name: Install project from main branch
5797
run: uv sync --dev
98+
working-directory: _main_branch
5899

59100
- name: Install just
60101
run: uv tool install rust-just
@@ -119,12 +160,15 @@ jobs:
119160
120161
print("Workbook structure OK.")
121162
PYEOF
163+
working-directory: _main_branch
122164

123165
- name: Run round-trip diff (excel-to-schema)
124166
if: steps.inbox_check.outputs.present == 'true'
125167
id: roundtrip
126168
run: |
127-
OUTPUT=$(uv run python scripts/excel_to_schema.py "${{ env.INBOX_FILE }}" 2>&1)
169+
# Path to inbox file is relative to the repo root, one level up from _main_branch/
170+
OUTPUT=$(uv run python scripts/excel_to_schema.py \
171+
"../${{ env.INBOX_FILE }}" 2>&1)
128172
echo "$OUTPUT"
129173
130174
{
@@ -138,6 +182,7 @@ jobs:
138182
else
139183
echo "status=diff" >> "$GITHUB_OUTPUT"
140184
fi
185+
working-directory: _main_branch
141186

142187
- name: Post PR comment
143188
if: steps.inbox_check.outputs.present == 'true'
@@ -152,9 +197,19 @@ jobs:
152197
? "Excel inbox — round-trip validation **passed**"
153198
: "Excel inbox — round-trip validation **failed**";
154199
const advice = status === "ok"
155-
? "The workbook is aligned with the schema. Once this PR is merged, `docs/assets/coremeta4cat_vocabulary.xlsx` will be regenerated automatically from the schema."
200+
? "The workbook is aligned with the schema. The inbox file will be removed from this PR automatically. Once merged, `docs/assets/coremeta4cat_vocabulary.xlsx` will be regenerated from the schema."
156201
: "Differences were found between the inbox workbook and the schema.\n\nIf your changes require additions to the schema, please update the YAML source files and include those changes in this PR. The workbook is derived output — the schema is the ground truth.";
157202
203+
// Avoid duplicate comments on re-runs
204+
const { data: comments } = await github.rest.issues.listComments({
205+
owner: context.repo.owner,
206+
repo: context.repo.repo,
207+
issue_number: context.issue.number,
208+
});
209+
const existing = comments.find(c =>
210+
c.user.type === "Bot" && c.body.includes("Excel inbox")
211+
);
212+
158213
const body = [
159214
`## ${icon} ${title}`,
160215
"",
@@ -168,12 +223,21 @@ jobs:
168223
advice,
169224
].join("\n");
170225
171-
await github.rest.issues.createComment({
172-
owner: context.repo.owner,
173-
repo: context.repo.repo,
174-
issue_number: context.issue.number,
175-
body,
176-
});
226+
if (existing) {
227+
await github.rest.issues.updateComment({
228+
owner: context.repo.owner,
229+
repo: context.repo.repo,
230+
comment_id: existing.id,
231+
body,
232+
});
233+
} else {
234+
await github.rest.issues.createComment({
235+
owner: context.repo.owner,
236+
repo: context.repo.repo,
237+
issue_number: context.issue.number,
238+
body,
239+
});
240+
}
177241
178242
- name: Fail if round-trip has differences
179243
if: >
@@ -182,3 +246,14 @@ jobs:
182246
run: |
183247
echo "::error::Round-trip validation failed. See the PR comment for details."
184248
exit 1
249+
250+
- name: Remove inbox file from PR branch
251+
if: >
252+
steps.inbox_check.outputs.present == 'true' &&
253+
steps.roundtrip.outputs.status == 'ok'
254+
run: |
255+
git config user.name "github-actions[bot]"
256+
git config user.email "github-actions[bot]@users.noreply.github.com"
257+
git rm "${{ env.INBOX_FILE }}"
258+
git commit -m "ci: remove validated inbox Excel (will be regenerated from schema on merge)"
259+
git push

0 commit comments

Comments
 (0)