Skip to content

Commit f2d3b33

Browse files
Kristopher Turnerclaude
andcommitted
chore(platform): close remaining epic gaps — ADR-0005, script fix, project ID
- decisions/0005-reusable-workflow-split.md formalises the .github vs platform reusable-workflow split rule (closes ADR-0001 §pending) - New-AzureLocalRepo.ps1: pull remote LICENSE before push (gh repo create --license MIT auto-seeds main); fail loudly on push errors instead of falsely logging PASS. Adds -SolutionOptionId parameter + {{SOLUTION_OPTION_ID}} token so add-to-project.yml is no longer templated with a placeholder - _common/add-to-project.yml: token instead of REPLACE_WITH placeholder - platform/add-to-project.yml: real Platform option GUID 9c3da2ef - decisions/README.md: index now lists ADR-0003, 0004, 0005 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 759a033 commit f2d3b33

6 files changed

Lines changed: 95 additions & 7 deletions

File tree

.github/workflows/add-to-project.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ jobs:
1111
uses: AzureLocal/.github/.github/workflows/reusable-add-to-project.yml@main
1212
with:
1313
id-prefix: PLATFORM
14-
# TODO: Update solution-option-id to the GUID for the "Platform" option on the Azure Local Solutions project board.
15-
# The ID can be found via: gh api graphql -f query='{ organization(login: "AzureLocal") { projectV2(number: 3) { fields(first: 20) { nodes { ... on ProjectV2SingleSelectField { name options { id name } } } } } } }'
16-
solution-option-id: REPLACE_WITH_SOLUTION_OPTION_ID
14+
solution-option-id: '9c3da2ef'
1715
secrets:
1816
AZL_APP_ID: ${{ secrets.AZL_APP_ID }}
1917
AZL_APP_PRIVATE_KEY: ${{ secrets.AZL_APP_PRIVATE_KEY }}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# ADR 0005 — Reusable workflow split between `AzureLocal/.github` and `AzureLocal/platform`
2+
3+
- **Status**: Accepted
4+
- **Date**: 2026-04-13
5+
- **Deciders**: @kristopherjturner
6+
7+
## Context
8+
9+
Reusable GitHub Actions workflows for the AzureLocal org could plausibly live in either of two existing repos:
10+
11+
- [`AzureLocal/.github`](https://github.com/AzureLocal/.github) — the org's GitHub-metadata repo. Already hosts `reusable-add-to-project.yml`, `reusable-release-please.yml`, `reusable-validate-structure.yml`.
12+
- [`AzureLocal/platform`](https://github.com/AzureLocal/platform) — the org's developer-tooling repo created by [ADR-0001](./0001-create-platform-repo.md). Hosts standards, testing frameworks, and templates.
13+
14+
Without a written rule, every new reusable workflow becomes a judgement call about where it should live. ADR-0001 deferred this decision; [`docs/reusable-workflows/split-rule.md`](../docs/reusable-workflows/split-rule.md) sketched the rule informally. This ADR formalises it.
15+
16+
The rule has practical consequences: each repo has its own release cadence (`.github` rarely changes, `platform` evolves with frameworks), its own contributor audience, and a different blast radius if a workflow regresses.
17+
18+
## Decision
19+
20+
Adopt the **stack-vs-governance split**:
21+
22+
> **`AzureLocal/.github` owns governance workflows — those that apply to every repo regardless of stack.**
23+
>
24+
> **`AzureLocal/platform` owns stack-specific workflows — those that apply only to certain repo types (PS module, TS web app, IaC, MkDocs site, MAPROOM consumer).**
25+
26+
Plus three corollaries:
27+
28+
1. **Single-consumer workflows are not reusable.** If only one repo uses a workflow, keep it local to that repo. A reusable workflow must have at least two plausible consumers.
29+
2. **A workflow that's both universal and stack-specific** (e.g., release-please publishing to PSGallery) splits along the seam: the governance half goes to `.github`, the stack-specific half goes to `platform`.
30+
3. **New universal workflows default to `.github`.** If one ends up in `platform` for convenience, revisit at the next major version.
31+
32+
## Consequences
33+
34+
### Positive
35+
36+
- **Discovery is predictable.** A contributor browsing `AzureLocal/.github` for "how do I contribute" doesn't wade past TypeScript build internals.
37+
- **Release cadences are decoupled.** `platform` can ship breaking changes to a stack-specific workflow without forcing a `.github` release.
38+
- **Blast radius is bounded.** A bad governance workflow breaks every repo. A bad stack-specific workflow breaks only the repos in that stack. The split limits the worst case.
39+
- **Authority is clear for cross-repo conformance.** `Test-RepoConformance` (in `AzureLocal.Common`) only has to check that `deploy-docs.yml` references `AzureLocal/platform/.github/workflows/reusable-mkdocs-deploy.yml@<ref>` — never `.github` — because docs deployment is stack-specific by definition.
40+
41+
### Negative
42+
43+
- **Two repos to track for workflow versions.** Consumers pin two refs separately (`@v1` for `.github`, `@v1` for `platform`). Mitigated by documenting the canonical pin patterns in [`docs/reusable-workflows/consumer-patterns.md`](../docs/reusable-workflows/consumer-patterns.md).
44+
- **Edge cases require a judgement.** When a workflow blurs the governance/stack line, the corollary in §Decision applies — but applying it correctly requires the maintainer to think carefully. ADR doesn't remove the judgement, only makes it consistent.
45+
- **Two ADR-required surfaces.** Adding a new reusable workflow to either repo now requires an ADR. Documented overhead, accepted as the cost of explicit decisions.
46+
47+
### Neutral
48+
49+
- The current lineup (`reusable-add-to-project`, `reusable-release-please`, `reusable-validate-structure` in `.github`; the six `reusable-*` workflows in `platform`) already follows the rule. This ADR codifies the existing state, no migration required.
50+
51+
### Affected repos / owners
52+
53+
- **`AzureLocal/.github`**: no change today. Future governance workflows land here.
54+
- **`AzureLocal/platform`**: no change today. Future stack-specific workflows land here.
55+
- **All consumer repos**: no change today. Already pin against the correct repos per the rule.
56+
- **Maintainer**: every new reusable workflow proposal should cite this ADR and state which side of the rule it falls on.
57+
58+
## Alternatives considered
59+
60+
- **Single repo for all reusable workflows** — rejected. Discovery and blast-radius arguments above. The cost of a second repo (already paid — both repos exist) is lower than the cost of mixing governance and stack content.
61+
- **Per-stack repos** (`AzureLocal/ps-workflows`, `AzureLocal/ts-workflows`, etc.) — rejected. Would multiply the number of repos a consumer pins and the number of major-version axes to track. The current two-repo split is the minimum viable separation.
62+
- **Live in the consumer repos themselves; share via copy-paste** — rejected. This is the drift problem [ADR-0001](./0001-create-platform-repo.md) and [ADR-0002](./0002-standards-single-source.md) already solved for standards. Rejected on the same grounds.
63+
- **Keep the rule informal in `docs/reusable-workflows/split-rule.md` only** — rejected. Without an ADR the rule is a documentation note, not a governing decision. ADR makes it citable and reviewable.
64+
65+
## Status
66+
67+
Accepted 2026-04-13. Supersedes the "ADR 0004 — reusable-workflow-split" placeholder referenced in [ADR-0001](./0001-create-platform-repo.md). (ADR-0004 was repurposed for testing-toolset classification.)
68+
69+
The rule is also documented in user-facing form at [`docs/reusable-workflows/split-rule.md`](../docs/reusable-workflows/split-rule.md). When the ADR and the docs page diverge, this ADR wins; update the docs page accordingly.

decisions/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ Lightweight, numbered, immutable records of cross-cutting decisions that affect
88
|---|---|---|
99
| [0001](./0001-create-platform-repo.md) | Create AzureLocal platform repo | Accepted |
1010
| [0002](./0002-standards-single-source.md) | Standards as single source of truth | Accepted |
11-
| `0003` | MAPROOM & IIC canon (pending Phase 2) | Proposed |
12-
| `0004` | Reusable workflow split between .github and platform | Proposed |
11+
| [0003](./0003-maproom-iic-canon.md) | MAPROOM framework & IIC canon centralization | Accepted |
12+
| [0004](./0004-testing-toolset-classification.md) | Testing toolset classification rubric | Accepted |
13+
| [0005](./0005-reusable-workflow-split.md) | Reusable workflow split between .github and platform | Accepted |
1314

1415
## When to write an ADR
1516

docs/reusable-workflows/split-rule.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,4 @@ Ask one question: *does every AzureLocal repo benefit from this, or only a subse
6767

6868
- [Consumer patterns](consumer-patterns.md) — copy-paste examples
6969
- [Versioning](versioning.md) — how to pin, how to bump
70-
- [ADR 0004 (pending) — reusable-workflow-split](https://github.com/AzureLocal/platform/blob/main/decisions/0004-reusable-workflow-split.md)
70+
- [ADR 0005 — reusable-workflow-split](https://github.com/AzureLocal/platform/blob/main/decisions/0005-reusable-workflow-split.md)

repo-management/org-scripts/New-AzureLocalRepo.ps1

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ param(
7070

7171
[string]$ModuleName,
7272

73+
[Parameter(HelpMessage='GUID for the matching option on the AzureLocal Solutions project board (Solution field). Look up via: gh api graphql -f query=''{ organization(login: "AzureLocal") { projectV2(number: 3) { fields(first: 30) { nodes { ... on ProjectV2SingleSelectField { name options { id name } } } } } } }''')]
74+
[string]$SolutionOptionId = '',
75+
7376
[switch]$DryRun,
7477
[switch]$SkipBranchProtection,
7578
[switch]$SkipLabels,
@@ -113,6 +116,12 @@ $tokens = @{
113116
'{{MODULE_GUID}}' = [guid]::NewGuid().ToString()
114117
'{{ID_PREFIX}}' = $ModuleName.ToUpper()
115118
'{{MAPROOM}}' = if ($Type -eq 'ps-module') { 'true' } else { 'false' }
119+
'{{SOLUTION_OPTION_ID}}' = $SolutionOptionId
120+
}
121+
122+
if (-not $SolutionOptionId) {
123+
Log 'WARN' "No -SolutionOptionId supplied. add-to-project.yml will need a manual edit before the workflow runs successfully."
124+
Log 'INFO' "Look up the GUID with: gh api graphql -f query='{ organization(login: \"AzureLocal\") { projectV2(number: 3) { fields(first: 30) { nodes { ... on ProjectV2SingleSelectField { name options { id name } } } } } } }'"
116125
}
117126

118127
# Workflows adopted
@@ -214,9 +223,20 @@ try {
214223
git config user.email 'github-actions[bot]@users.noreply.github.com'
215224
git config user.name 'github-actions[bot]'
216225
git remote add origin "https://github.com/AzureLocal/$Name.git"
226+
227+
# gh repo create --license MIT seeds remote main with a LICENSE commit.
228+
# Pull it in so the scaffold push isn't rejected as non-fast-forward.
229+
git fetch origin main --quiet 2>$null
230+
if ($LASTEXITCODE -eq 0) {
231+
git reset --soft FETCH_HEAD --quiet
232+
}
233+
217234
git add -A
218235
git commit -m "chore: scaffold $Name from platform template ($Type)" --quiet
219236
git push -u origin main --quiet
237+
if ($LASTEXITCODE -ne 0) {
238+
throw "git push to AzureLocal/$Name failed (exit $LASTEXITCODE) — scaffold not on remote."
239+
}
220240
Log PASS "Initial commit pushed."
221241
} finally {
222242
Pop-Location

templates/_common/.github/workflows/add-to-project.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
uses: AzureLocal/.github/.github/workflows/reusable-add-to-project.yml@main
1212
with:
1313
id-prefix: {{ID_PREFIX}}
14-
solution-option-id: REPLACE_WITH_SOLUTION_OPTION_ID
14+
solution-option-id: '{{SOLUTION_OPTION_ID}}'
1515
secrets:
1616
AZL_APP_ID: ${{ secrets.AZL_APP_ID }}
1717
AZL_APP_PRIVATE_KEY: ${{ secrets.AZL_APP_PRIVATE_KEY }}

0 commit comments

Comments
 (0)