Commit fd11d1f
ci: add launcher-standard prose↔a2ml lock-step gate (#172)
## Summary
`launcher/README.adoc` §Sync requirement declares that the
machine-readable form (`launcher/launcher-standard.a2ml`) and the prose
form (`docs/UX-standards/launcher-standard.adoc`) MUST be edited in the
same PR, and calls drift between them a *"standards-compliance bug"* —
but nothing enforced the rule. The recently-fixed cross-doc
contradictions in
[#170](#170)
were exactly the drift this rule was meant to prevent.
This adds `.github/workflows/launcher-standard-lockstep.yml` — a hard
gate that runs on every PR touching either file. If only one is in the
diff, it fails with a message naming the missing partner and pointing
back at §Sync requirement.
## How it works
\`\`\`yaml
on:
pull_request:
paths:
- 'launcher/launcher-standard.a2ml'
- 'docs/UX-standards/launcher-standard.adoc'
\`\`\`
GitHub's `paths:` filter ensures the workflow only triggers when one of
the two files is touched (cheap — no wasted runs for unrelated PRs).
Inside the job, a 30-line bash check diffs `pull_request.base.sha`
against `pull_request.head.sha`, computes which of the two files are
*touched* vs *missing*, and fails iff the touched set is non-empty but
incomplete.
## Dry-run verification
Logic dry-run locally against six synthetic diffs:
| Diff | Expected | Result |
|------|----------|--------|
| only a2ml touched | FAIL — drift | FAIL ✓ |
| only adoc touched | FAIL — drift | FAIL ✓ |
| both touched | PASS | PASS ✓ |
| both touched + unrelated | PASS | PASS ✓ |
| neither touched | skip (no trigger) | workflow doesn't run ✓ |
| a2ml + unrelated (drift) | FAIL — drift | FAIL ✓ |
## Pattern conformance
Modelled on the existing `makefile-blocker.yml` gate:
- Same SPDX header.
- Same `actions/checkout@de0fac2…` pin (verified live against
`github.com/actions/checkout` — corresponds to v4 commit
`de0fac2e4500dabe0009e67214ff5f5447ce83dd` "Fix tag handling" #2356).
- Same `concurrency` block that cancels superseded runs (safe — this is
a read-only check, no mutations).
- Same `permissions: contents: read`.
## Scope notes
- **Initial group is the 2-file pair** the README literally calls out.
Once [#171](#171)
lands and introduces `launcher/resolve-desktop-tools.sh`, a follow-up
should extend the group to include the resolver (which mirrors
`[resolution]` in the a2ml and currently relies on reviewer
discipline).
- **Hard fail, not warning.** The README uses "MUST" / "is a bug"
language; the gate matches that strictness. Genuinely one-sided
edits (e.g. a prose-only typo fix with no a2ml equivalent) need a
no-op whitespace touch to the other file — the error message
documents this escape.
- **Not added to required-checks list.** Branch-protection config
lives outside the repo. Owner can promote this to a required check
via the UI or `.github/settings.yml` once they've seen it pass green
on a few PRs.
## Coordination
- Fully independent of #170 and #171 — touches a different file
(`.github/workflows/launcher-standard-lockstep.yml`). No conflicts
in any merge order.
## Test plan
- [x] Dry-run against 6 synthetic diff scenarios — all expected outcomes
- [x] actions/checkout SHA pin verified live
- [x] YAML syntactically valid (workflow loads — visible in Actions tab
once branch is pushed)
- [ ] First live trigger will happen when PR #170 or #171 next pushes
and touches a group file — at that point this gate's run will
appear on those PRs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 4bc34e6 commit fd11d1f
1 file changed
Lines changed: 98 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
0 commit comments