ci(udg): build bot — add the build-dist label and CI runs just build #193

Merged
peterswimm merged 1 commit from ci/udg-build-bot into main 2026-10-07 23:02:44 +00:00
Owner

Scope

Adds the UDG build bot, so designers (Keff first) get dist/ rebuilt without a local
toolchain. Add the build-dist label to a same-repo PR, or dispatch the workflow with a branch,
and CI:

  1. runs the justfile's build recipe in python:3.12-slim;
  2. converts EPS plates, but only when the branch changes one (it installs ghostscript and
    poppler-utils itself);
  3. refuses to push if anything outside the generated paths changed (dist/,
    Brand/source/derived/, docs/brand-guide.html, the WordPress theme and preview outputs);
  4. commits the result back to the branch and removes the label.

It never runs for forks or main. A "Let CI build it" section in CONTRIBUTING.md explains it.

This is step 1 of the designer-tooling roadmap. Step 2 is just doctor and a designer quickstart.

Setup needed before it can run (repo admin)

  • Create the label build-dist.
  • Add the repo secret UDG_BUILD_BOT_TOKEN: a dedicated bot account's token with write
    access to this repository only (contents, plus issues for removing the label). Don't reuse
    CI_TOKEN. Forgejo's automatic token won't work, because its pushes don't re-trigger the drift
    gate.
  • Note: for pull_request events, the workflow file from the PR head runs. Anyone who can push a
    branch here could therefore read this token, but they already have write access, so it grants
    nothing new. Keep the token scoped to this repository.

Testing

Rehearsed in python:3.12-slim (Docker) with the workflow's own run: blocks, extracted from the
YAML; only git push was stubbed:

Case Expected Result
main with stale artifact.html (the control, which should produce a commit) bot commit 13 × artifact.html, identical to #192
#192 branch, already built no commit "dist/ already matches … nothing to commit"
branch that edits an EPS plate plates converted Brand/source/derived/plate.ui.rectangle.svg + dist/assets/* committed
stray non-generated file guard fails ::error::build changed non-generated paths; nothing pushed: stray.txt

The extract, artifact, brand-guide, assets and docs checks all pass on both bot-made commits.
test_workflow_invariants.py: 62 passed. yamllint --strict .forgejo/workflows/: clean. Not yet
exercised live: the push and the label removal, which need the secret above.

Impact Statement

  • Existing features: no expected impact. A new opt-in workflow; udg-dist-check is unchanged.
  • Public APIs / contracts: none.
  • Schemas: none.
  • Data / artifacts: the bot writes only generated paths, guarded.
  • Integrations: Forgejo Actions plus one new repo secret and one new label.
  • Mappings: none.
  • Backward compatibility: fully compatible. Running just build locally still works.

Docs

CONTRIBUTING.md → "Let CI build it". Merge #192 first so the drift gate is green.

## Scope Adds the **UDG build bot**, so designers (Keff first) get `dist/` rebuilt without a local toolchain. Add the `build-dist` label to a same-repo PR, or dispatch the workflow with a branch, and CI: 1. runs the justfile's `build` recipe in `python:3.12-slim`; 2. converts EPS plates, but only when the branch changes one (it installs ghostscript and poppler-utils itself); 3. **refuses to push** if anything outside the generated paths changed (`dist/`, `Brand/source/derived/`, `docs/brand-guide.html`, the WordPress theme and preview outputs); 4. commits the result back to the branch and removes the label. It never runs for forks or `main`. A "Let CI build it" section in `CONTRIBUTING.md` explains it. This is step 1 of the designer-tooling roadmap. Step 2 is `just doctor` and a designer quickstart. ### Setup needed before it can run (repo admin) - [ ] Create the label **`build-dist`**. - [ ] Add the repo secret **`UDG_BUILD_BOT_TOKEN`**: a dedicated bot account's token with write access to **this repository only** (contents, plus issues for removing the label). Don't reuse `CI_TOKEN`. Forgejo's automatic token won't work, because its pushes don't re-trigger the drift gate. - Note: for `pull_request` events, the workflow file from the PR head runs. Anyone who can push a branch here could therefore read this token, but they already have write access, so it grants nothing new. Keep the token scoped to this repository. ## Testing Rehearsed in `python:3.12-slim` (Docker) with the workflow's own `run:` blocks, extracted from the YAML; only `git push` was stubbed: | Case | Expected | Result | |---|---|---| | `main` with stale `artifact.html` (the control, which should produce a commit) | bot commit | 13 × `artifact.html`, identical to #192 | | #192 branch, already built | no commit | "dist/ already matches … nothing to commit" | | branch that edits an EPS plate | plates converted | `Brand/source/derived/plate.ui.rectangle.svg` + `dist/assets/*` committed | | stray non-generated file | guard fails | `::error::build changed non-generated paths; nothing pushed: stray.txt` | The extract, artifact, brand-guide, assets and docs checks all pass on both bot-made commits. `test_workflow_invariants.py`: 62 passed. `yamllint --strict .forgejo/workflows/`: clean. Not yet exercised live: the push and the label removal, which need the secret above. ## Impact Statement - Existing features: no expected impact. A new opt-in workflow; `udg-dist-check` is unchanged. - Public APIs / contracts: none. - Schemas: none. - Data / artifacts: the bot writes only generated paths, guarded. - Integrations: Forgejo Actions plus one new repo secret and one new label. - Mappings: none. - Backward compatibility: fully compatible. Running `just build` locally still works. ## Docs `CONTRIBUTING.md` → "Let CI build it". Merge #192 first so the drift gate is green.
ci(udg): build bot — add the build-dist label and CI runs just build
Some checks failed
PR policy check / policy-gate (pull_request) Successful in 1s
Test Generator / lint-workflows (pull_request) Successful in 24s
Security Scans / Gitleaks secret scan (pull_request) Successful in 27s
Test Generator / lint-prose (pull_request) Successful in 30s
Security Scans / Trivy filesystem scan (pull_request) Failing after 33s
UDG dist drift gate / dist/ matches dtcg + udg sources (pull_request) Failing after 15s
Copy coverage / Design copy reaching rendered HTML (pull_request) Successful in 26s
Block Validation / validate (pull_request) Successful in 59s
Test Generator / test (pull_request) Successful in 3m29s
443d688b1a
udg-dist-check only verifies dist/, so every contributor had to run the
build locally (terminal, Python, just, plus ghostscript + poppler for
plates). #163 shows the cost: a generator change merged without its rebuild
and the drift gate stayed red on every PR after it.

New workflow udg-build-bot.yml: on the build-dist label (same-repo PRs
only) or workflow_dispatch with a branch (never main), it runs the `build`
recipe in python:3.12-slim, converts EPS plates only when the branch
touches one, refuses to push if anything outside the generated paths
changed, commits the generated output back to the branch with a
dedicated repo-scoped token (UDG_BUILD_BOT_TOKEN), and removes the label.

Rehearsed in python:3.12-slim with the workflow's own run: blocks:
  - origin/main (control): bot commit = the 13 stale artifact.html (#192)
  - #192 branch: "nothing to commit"
  - branch touching an EPS: plates converted, derived SVG + asset index committed
  - stray non-generated file: guard fails, nothing pushed
Drift checks pass on both bot commits. Workflow invariants: 62 passed;
yamllint --strict: clean.
peterswimm deleted branch ci/udg-build-bot 2026-10-07 23:02:45 +00:00
Sign in to join this conversation.
No description provided.