Skip to content

Commit 9609b09

Browse files
committed
desktops: document the matrix audit automation
Adds a "Matrix audit automation" section to the desktops developer guide describing the weekly GitHub Actions workflow that audits the YAML matrix and opens bot PRs for drift. Covers: - the two drift classes (missing releases, package holes) and why the deterministic scanner + LLM applier split exists - audit.py report shape, armbian/build dependency, and flags - audit_prompt.py constraints pinned into the Claude prompt - maintenance-desktop-audit.yml triggers, concurrency, each step, the claude-code-action tool allow-list required for edits to actually land, and the execution-log artifact used for diagnosing zero-edit runs - permissions block - a maintainer review checklist for incoming draft bot PRs
1 parent eae3cdd commit 9609b09

1 file changed

Lines changed: 105 additions & 0 deletions

File tree

docs/Developer-Guide_Desktops.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,111 @@ This makes the same code path usable for image preseeding inside Docker without
736736

737737
The `*07-*09` change-tier entries use `module_desktops set-tier` and gate visibility with `module_desktops status de=<X> && ! module_desktops at-tier de=<X> tier=<target>`.
738738

739+
## Matrix audit automation
740+
741+
The desktop matrix covers several DEs × several releases × several architectures, and two kinds of drift tend to accumulate silently:
742+
743+
1. **Missing releases**`armbian/build` adds a new release to `config/distributions/` (e.g. Ubuntu `resolute`) but no DE YAML grows a release block for it, so the desktop can't be installed on that release at all.
744+
2. **Package holes** — an entry in the resolved `DESKTOP_PACKAGES` set is no longer published for some `(release, arch)` pair (archive removed it, or it was never built for that arch), so `apt` fails at install time with `E: Unable to locate package`.
745+
746+
A weekly GitHub Actions workflow detects both, hands the findings to Claude Code to propose YAML edits, and opens a draft PR for a maintainer to review.
747+
748+
### Components
749+
750+
```text
751+
tools/modules/desktops/github/
752+
├── audit.py # deterministic scanner — emits audit-report.json
753+
├── audit_prompt.py # renders the report into a Claude prompt
754+
└── audit_apply.py # legacy direct-API applier (unused by the workflow)
755+
756+
.github/workflows/
757+
└── maintenance-desktop-audit.yml # the scheduled workflow
758+
```
759+
760+
Only the scanner talks to the network; the LLM never fetches package metadata itself. That keeps the "what is broken" signal reproducible and cache-friendly, and confines all non-determinism to the "how should we fix it" step.
761+
762+
### `audit.py`
763+
764+
Walks `tools/modules/desktops/yaml/` against:
765+
766+
- `armbian/build`'s `config/distributions/<release>.conf` (loaded from a sibling checkout passed via `--build-repo`) to get the set of releases and their support statuses (`supported`, `csc`, `eos`, …). Anything `eos` is skipped.
767+
- `packages.debian.org` and `packages.ubuntu.com` — one `urllib` request per `(release, arch, package)` tuple, parallelised with `ThreadPoolExecutor`. Responses are cached in-process for the run.
768+
769+
Report shape (`audit-report.json`):
770+
771+
```json
772+
{
773+
"scanned_releases": ["bookworm", "noble", "plucky", "trixie"],
774+
"build_distributions": { "<release>": { "name": "...", "support": "supported|csc|eos", "architectures": [...] } },
775+
"missing_releases": [ { "release": "resolute", "support_status": "csc", "architectures": [...] } ],
776+
"package_holes": [ { "de": "xfce", "release": "trixie", "arch": "riscv64", "tier": "full", "missing": ["libfoo"] } ],
777+
"skipped_desktops": ["bianbu", "budgie", "deepin", "kde-neon"],
778+
"stats": { "desktops": 8, "scope": 4, "holes": 0, "package_lookups": 0 }
779+
}
780+
```
781+
782+
Desktops with `status: unsupported` in their YAML are listed in `skipped_desktops` and not audited — drift in an unsupported DE isn't actionable.
783+
784+
Flags: `--tier {minimal,mid,full}` narrows the scope; `--release <codename>` audits a single release; `--skip-network` is a dry-run that only reports `missing_releases`.
785+
786+
### `audit_prompt.py`
787+
788+
Renders the JSON report into a single text prompt (no markdown-in-markdown gymnastics; the report JSON is embedded in fenced blocks). The prompt pins Claude to:
789+
790+
- touch only YAML files under `tools/modules/desktops/yaml/`
791+
- address **every** finding, not just the first
792+
- prefer edits to `common.yaml`'s `tier_overrides` block for package holes (one place, applies to every DE) over duplicating `packages_remove` entries in per-DE YAMLs
793+
- for missing releases, add a release block to each `status: supported` DE YAML, copying the shape from an existing block and adjusting per-release deltas only where needed
794+
- always add an inline comment explaining **why** a hole exists, so future readers can distinguish a transient archive gap from a permanent upstream-port limitation
795+
- preserve the existing 2-space indentation
796+
- if the report is empty, say so and make no edits
797+
798+
### `maintenance-desktop-audit.yml`
799+
800+
Triggers:
801+
802+
- `schedule: '0 6 * * 1'` — Mondays 06:00 UTC. Release and package availability change slowly, so weekly is enough and cheap.
803+
- `workflow_dispatch` — with optional `tier`, `release`, and `dry_run` inputs. `dry_run: true` stops after the deterministic audit and attaches `audit-report.json` without calling Claude or opening a PR.
804+
805+
Concurrency: `group: desktop-audit`, `cancel-in-progress: false` — two scheduled runs will never race, and a manual dispatch queues behind the scheduled run rather than killing it.
806+
807+
Job steps, in order:
808+
809+
1. **Checkout configng** at the workspace root (no `path:`) so `claude-code-action` finds `.git`.
810+
2. **Checkout `armbian/build`** into `armbian-build/` with `fetch-depth: 1` — the audit only reads `config/distributions/`, so shallow is fine.
811+
3. **Set up Python 3.12** and `pip install pyyaml`.
812+
4. **Run `audit.py`** — writes `audit-report.json`, appends a markdown summary table to `$GITHUB_STEP_SUMMARY`, and sets `steps.audit.outputs.actionable` to `true` iff `missing_releases` or `package_holes` is non-empty.
813+
5. **Prepare Claude prompt** (`audit_prompt.py`) — only if `actionable` and not a dry run.
814+
6. **Upload `audit-report`** artifact (always, 30-day retention) — useful even on zero-hole runs as historical record.
815+
7. **`anthropics/claude-code-action@v1`** with:
816+
- `claude_code_oauth_token: secrets.CLAUDE_CODE_OAUTH_TOKEN` (Max subscription token — no per-run API charges).
817+
- `claude_args: --max-turns 30 --permission-mode acceptEdits --allowed-tools Edit,Write,Read,Glob,Grep,Bash(git:*)`. `acceptEdits` plus the explicit allow-list is required: without them the action's default tool gate denies Edit/Write and the branch stays empty. `Bash(git:*)` only permits read-only git inspection; no shell execution surface.
818+
8. **Stash Claude execution log** — copies `${RUNNER_TEMP}/claude-execution-output.json` into the workspace; uploaded as the `claude-execution-output` artifact with `if: always()` so a failed or zero-edit run is debuggable from the transcript without a re-run.
819+
9. **Clean up temp files** — removes `armbian-build/`, `audit-report.json`, `claude-prompt.txt`, and `claude-execution-output.json` from the working tree so `peter-evans/create-pull-request` sees only Claude's YAML edits.
820+
10. **`peter-evans/create-pull-request@v6`** — branch `bot/desktop-matrix-audit`, base `main`, `add-paths: tools/modules/desktops/yaml/*`, `delete-branch: true`, `draft: true`, labels `bot`, `desktops`, `documentation`. PR body is `steps.claude.outputs.structured_output` (Claude's own summary of what it changed and why). If Claude produced no diff, the branch is not ahead of main and no PR is opened — the workflow finishes green with only the audit artifact.
821+
822+
### Permissions
823+
824+
```yaml
825+
permissions:
826+
contents: write # push to bot/desktop-matrix-audit
827+
pull-requests: write # open the PR
828+
id-token: write # claude-code-action OIDC
829+
```
830+
831+
### Reviewing a bot PR
832+
833+
Bot PRs open as **draft** on purpose. A human check before merge:
834+
835+
1. Read Claude's PR body — it should list every file it changed and the reason.
836+
2. Confirm the diff is scoped to `tools/modules/desktops/yaml/`. Any out-of-scope file is a red flag (the workflow's `add-paths` should already prevent this, but verify).
837+
3. For each missing-release addition: spot-check that the new release block is a sensible copy of an existing one (e.g. a `resolute` block for `xfce.yaml` should look like the `trixie` or `noble` block, not a half-written stub).
838+
4. For each package-hole edit: confirm it lives in `common.yaml`'s `tier_overrides` where it belongs, not duplicated per-DE.
839+
5. For each WHY comment: confirm it's accurate. "not yet in trixie" ages out; "no upstream riscv64 port" doesn't.
840+
6. Mark ready for review and merge normally. `delete-branch: true` cleans up on merge.
841+
842+
If Claude judged the report non-actionable (e.g. the only finding is a `csc`-tier release a maintainer wants to hold off on), the run ends with the `audit-report` artifact present and no PR — inspect the artifact and the `claude-execution-output` log to confirm.
843+
739844
## Common pitfalls
740845
741846
### packages_uninstall cascade

0 commit comments

Comments
 (0)