Skip to content

Commit c9713dd

Browse files
Validate-only Excel inbox; add updater
Make the inbox workflow validation-only: remove promotion/commit steps and INBOX_PROMOTE_STRATEGY, tighten checks and messages, and avoid writing back to PR branches (checkout no longer persists credentials). Improve Python/JS formatting and capture of round-trip output. Add a new update_excel workflow that runs on pushes to main to regenerate docs/assets/coremeta4cat_vocabulary.xlsx from the schema and commit it (with contents: write permission). This separates validation (safe for forks/PRs) from the single authoritative post-merge update that writes the generated workbook.
1 parent fd4e8e8 commit c9713dd

2 files changed

Lines changed: 125 additions & 101 deletions

File tree

.github/workflows/excel_inbox.yaml

Lines changed: 56 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,24 @@
11
# .github/workflows/excel_inbox.yaml
22
#
3-
# Excel Inbox — PR validation pipeline
4-
# =====================================
5-
# Triggered when a pull request adds or modifies a file inside the
6-
# inbox/ directory. The expected contributor flow is:
3+
# Excel Inbox — PR validation
4+
# ============================
5+
# Triggered when a pull request adds or modifies a file inside inbox/.
76
#
8-
# 1. Contributor downloads docs/assets/coremeta4cat_vocabulary.xlsx,
9-
# edits the vocabulary (adds/changes rows), and opens a PR placing
10-
# the updated file in:
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.
119
#
12-
# inbox/coremeta4cat_vocabulary.xlsx
10+
# Contributor flow:
11+
# 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.
1317
#
14-
# 2. This workflow runs automatically and:
15-
# a. Validates the workbook structure (sheets, columns).
16-
# b. Runs the round-trip diff (excel-to-schema): checks that every
17-
# top-level slot in the workbook exists in the schema with the
18-
# correct M/R/O value.
19-
# c. Posts a summary comment on the PR with the outcome.
20-
# d. On success: moves the file to docs/assets/ and commits it back
21-
# to the PR branch, OR deletes it and lets schema-to-excel
22-
# regenerate it (see INBOX_PROMOTE_STRATEGY below).
23-
# e. On failure: leaves the inbox file in place and marks the
24-
# workflow as failed so the PR cannot be merged.
25-
#
26-
# INBOX_PROMOTE_STRATEGY
27-
# ──────────────────────
28-
# Two strategies are supported, controlled by the env var below:
29-
#
30-
# "move" — The validated inbox file is moved to docs/assets/ and
31-
# committed back. Use this when contributors may have
32-
# added rows that the schema-to-excel script would not
33-
# regenerate (e.g. manually curated descriptions).
34-
#
35-
# "regenerate" — The inbox file is deleted and schema-to-excel is run
36-
# to produce a fresh docs/assets/ workbook from the
37-
# schema. Use this when the Excel is purely derived
38-
# output and should always mirror the schema exactly.
39-
#
40-
# Set the strategy in the env block below.
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.
4122
---
4223
name: Excel inbox validation
4324

@@ -48,26 +29,22 @@ on: # yamllint disable-line rule:truthy
4829

4930
env:
5031
FORCE_COLOR: "1"
51-
INBOX_PROMOTE_STRATEGY: "regenerate" # "move" or "regenerate"
5232
INBOX_FILE: "inbox/coremeta4cat_vocabulary.xlsx"
53-
DOCS_FILE: "docs/assets/coremeta4cat_vocabulary.xlsx"
5433

5534
permissions:
56-
contents: write # needed to push the commit back to the PR branch
5735
pull-requests: write # needed to post the summary comment
5836

5937
jobs:
6038
validate-inbox:
6139
runs-on: ubuntu-latest
62-
# Only run when the inbox file is actually present in the PR
40+
6341
steps:
6442

65-
- name: Check out PR branch
43+
- name: Check out repository
6644
uses: actions/checkout@v6.0.3
45+
# No ref override — uses the PR merge commit, works for forks too.
6746
with:
68-
ref: ${{ github.head_ref }}
69-
token: ${{ secrets.GITHUB_TOKEN }}
70-
fetch-depth: 0
47+
persist-credentials: false
7148

7249
- name: Install uv
7350
uses: astral-sh/setup-uv@v8.2.0
@@ -90,7 +67,7 @@ jobs:
9067
echo "Inbox file found: ${{ env.INBOX_FILE }}"
9168
else
9269
echo "present=false" >> "$GITHUB_OUTPUT"
93-
echo "No inbox file found — skipping validation."
70+
echo "No inbox file found at ${{ env.INBOX_FILE }} — nothing to validate."
9471
fi
9572
9673
- name: Validate workbook structure
@@ -102,10 +79,14 @@ jobs:
10279
import openpyxl
10380
10481
path = "${{ env.INBOX_FILE }}"
105-
expected_sheets = {"Introduction", "Legend", "CoreMeta4Cat",
106-
"Synthesis", "Characterization", "Reaction", "Simulation"}
107-
expected_headers = ["label", "type", "domain", "M / R / O",
108-
"range", "uri", "description"]
82+
expected_sheets = {
83+
"Introduction", "Legend", "CoreMeta4Cat",
84+
"Synthesis", "Characterization", "Reaction", "Simulation",
85+
}
86+
expected_headers = [
87+
"label", "type", "domain", "M / R / O",
88+
"range", "uri", "description",
89+
]
10990
data_sheets = ["Synthesis", "Characterization", "Reaction", "Simulation"]
11091
11192
errors = []
@@ -116,12 +97,10 @@ jobs:
11697
print(f"::error::Cannot open workbook: {e}")
11798
sys.exit(1)
11899
119-
# Check sheet presence
120100
missing = expected_sheets - set(wb.sheetnames)
121101
if missing:
122102
errors.append(f"Missing sheets: {', '.join(sorted(missing))}")
123103
124-
# Check headers on data sheets
125104
for sheet_name in data_sheets:
126105
if sheet_name not in wb.sheetnames:
127106
continue
@@ -130,28 +109,29 @@ jobs:
130109
if header != expected_headers:
131110
errors.append(
132111
f"Sheet '{sheet_name}' header mismatch.\n"
133-
f" Expected: {expected_headers}\n Got: {header}"
112+
f" Expected: {expected_headers}\n Got: {header}"
134113
)
135114
136115
if errors:
137116
for e in errors:
138117
print(f"::error::{e}")
139118
sys.exit(1)
140119
141-
print("Workbook structure is valid.")
120+
print("Workbook structure OK.")
142121
PYEOF
143122
144123
- name: Run round-trip diff (excel-to-schema)
145124
if: steps.inbox_check.outputs.present == 'true'
146125
id: roundtrip
147126
run: |
148-
# Run excel_to_schema against the inbox file and capture output.
149-
# The script exits 0 even with diffs, so we parse its output.
150127
OUTPUT=$(uv run python scripts/excel_to_schema.py "${{ env.INBOX_FILE }}" 2>&1)
151128
echo "$OUTPUT"
152-
echo "roundtrip_output<<EOF" >> "$GITHUB_ENV"
153-
echo "$OUTPUT" >> "$GITHUB_ENV"
154-
echo "EOF" >> "$GITHUB_ENV"
129+
130+
{
131+
echo "roundtrip_output<<EOF"
132+
echo "$OUTPUT"
133+
echo "EOF"
134+
} >> "$GITHUB_ENV"
155135
156136
if echo "$OUTPUT" | grep -q "OK Schema and workbook are fully aligned"; then
157137
echo "status=ok" >> "$GITHUB_OUTPUT"
@@ -167,15 +147,26 @@ jobs:
167147
script: |
168148
const status = "${{ steps.roundtrip.outputs.status }}";
169149
const output = process.env.roundtrip_output || "(no output)";
170-
const icon = status === "ok" ? "✅" : "❌";
171-
const title = status === "ok"
150+
const icon = status === "ok" ? "✅" : "❌";
151+
const title = status === "ok"
172152
? "Excel inbox — round-trip validation **passed**"
173153
: "Excel inbox — round-trip validation **failed**";
174-
const body = `## ${icon} ${title}\n\n<details><summary>Full diff output</summary>\n\n\`\`\`\n${output}\n\`\`\`\n</details>\n\n${
175-
status === "ok"
176-
? "The workbook is aligned with the schema. The file will be promoted to `docs/assets/` automatically."
177-
: "Differences were found between the inbox workbook and the schema. Please review the output above and either update the workbook or open a schema issue."
178-
}`;
154+
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."
156+
: "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.";
157+
158+
const body = [
159+
`## ${icon} ${title}`,
160+
"",
161+
"<details><summary>Full diff output</summary>",
162+
"",
163+
"```",
164+
output,
165+
"```",
166+
"</details>",
167+
"",
168+
advice,
169+
].join("\n");
179170
180171
await github.rest.issues.createComment({
181172
owner: context.repo.owner,
@@ -189,41 +180,5 @@ jobs:
189180
steps.inbox_check.outputs.present == 'true' &&
190181
steps.roundtrip.outputs.status != 'ok'
191182
run: |
192-
echo "::error::Round-trip validation failed. See PR comment for details."
183+
echo "::error::Round-trip validation failed. See the PR comment for details."
193184
exit 1
194-
195-
# ── Promotion step: move or regenerate ───────────────────────────────
196-
197-
- name: Promote inbox file (strategy = move)
198-
if: >
199-
steps.inbox_check.outputs.present == 'true' &&
200-
steps.roundtrip.outputs.status == 'ok' &&
201-
env.INBOX_PROMOTE_STRATEGY == 'move'
202-
run: |
203-
mv "${{ env.INBOX_FILE }}" "${{ env.DOCS_FILE }}"
204-
echo "Moved inbox file to docs/assets/."
205-
206-
- name: Promote inbox file (strategy = regenerate)
207-
if: >
208-
steps.inbox_check.outputs.present == 'true' &&
209-
steps.roundtrip.outputs.status == 'ok' &&
210-
env.INBOX_PROMOTE_STRATEGY == 'regenerate'
211-
run: |
212-
rm "${{ env.INBOX_FILE }}"
213-
just schema-to-excel
214-
echo "Removed inbox file; regenerated docs/assets/ from schema."
215-
216-
- name: Commit promotion back to PR branch
217-
if: steps.inbox_check.outputs.present == 'true' && steps.roundtrip.outputs.status == 'ok'
218-
run: |
219-
git config user.name "github-actions[bot]"
220-
git config user.email "github-actions[bot]@users.noreply.github.com"
221-
git add "${{ env.DOCS_FILE }}"
222-
# Stage inbox deletion if still present (move strategy leaves nothing to stage)
223-
git add "${{ env.INBOX_FILE }}" 2>/dev/null || true
224-
if git diff --cached --quiet; then
225-
echo "Nothing to commit."
226-
else
227-
git commit -m "chore: promote validated inbox Excel to docs/assets [skip ci]"
228-
git push
229-
fi
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# .github/workflows/update_excel.yaml
2+
#
3+
# Post-merge Excel update
4+
# ========================
5+
# Runs after every push to main (i.e. after a PR is merged).
6+
# Regenerates docs/assets/coremeta4cat_vocabulary.xlsx from the current
7+
# schema and commits it back if anything changed.
8+
#
9+
# This is the only place that writes the workbook — keeping write access
10+
# out of PR workflows entirely, which avoids fork permission issues.
11+
---
12+
name: Update Excel vocabulary
13+
14+
on: # yamllint disable-line rule:truthy
15+
push:
16+
branches: [main]
17+
paths:
18+
# Only regenerate when something that affects the workbook changes.
19+
- "src/coremeta4cat/schema/**"
20+
- "scripts/schema_to_excel.py"
21+
- "scripts/generate_schema_docs.py"
22+
23+
env:
24+
FORCE_COLOR: "1"
25+
26+
permissions:
27+
contents: write # needed to commit the regenerated workbook
28+
29+
jobs:
30+
update-excel:
31+
runs-on: ubuntu-latest
32+
33+
steps:
34+
35+
- name: Check out repository
36+
uses: actions/checkout@v6.0.3
37+
with:
38+
# Use a token with write access so the commit push works.
39+
# GITHUB_TOKEN is sufficient for same-repo pushes.
40+
token: ${{ secrets.GITHUB_TOKEN }}
41+
fetch-depth: 0
42+
43+
- name: Install uv
44+
uses: astral-sh/setup-uv@v8.2.0
45+
with:
46+
python-version: "3.12"
47+
enable-cache: true
48+
cache-dependency-glob: "uv.lock"
49+
50+
- name: Install project
51+
run: uv sync --dev
52+
53+
- name: Install just
54+
run: uv tool install rust-just
55+
56+
- name: Regenerate Excel vocabulary from schema
57+
run: just schema-to-excel
58+
59+
- name: Commit updated workbook if changed
60+
run: |
61+
git config user.name "github-actions[bot]"
62+
git config user.email "github-actions[bot]@users.noreply.github.com"
63+
git add docs/assets/coremeta4cat_vocabulary.xlsx
64+
if git diff --cached --quiet; then
65+
echo "Workbook unchanged — nothing to commit."
66+
else
67+
git commit -m "chore: regenerate vocabulary workbook from schema [skip ci]"
68+
git push
69+
fi

0 commit comments

Comments
 (0)