You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
Copy file name to clipboardExpand all lines: docs/Developer-Guide_Desktops.md
+105Lines changed: 105 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -736,6 +736,111 @@ This makes the same code path usable for image preseeding inside Docker without
736
736
737
737
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>`.
738
738
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.
├── 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.
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 forpackage holes (one place, applies to every DE) over duplicating `packages_remove` entriesin 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.
0 commit comments