Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
220 changes: 220 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
name: Release

# Builds a 0.1.0-alpha prerelease of azureauth-unofficial from main-v2 and creates a
# draft GitHub prerelease. The repository owner reviews and publishes each draft by hand.
# docs/delivery-wave.md ("Windows 0.1.0-alpha Prereleases") authorizes this workflow and
# bounds its permissions, assets and effects; the Windows design owns the package.

on:
workflow_dispatch:

permissions: {}

concurrency:
group: release
cancel-in-progress: false

env:
# The mise version CI pins; keep it equal to MISE_VERSION in check.yml.
MISE_VERSION: 2026.10.22

jobs:
build:
name: Build and package
runs-on: windows-latest
timeout-minutes: 45
permissions:
contents: read
outputs:
version: ${{ steps.version.outputs.version }}
commit: ${{ steps.version.outputs.commit }}
env:
DOTNET_CLI_TELEMETRY_OPTOUT: "1"
TESTINGPLATFORM_TELEMETRY_OPTOUT: "1"
DOTNET_NOLOGO: "1"
MISE_EXEC_AUTO_INSTALL: "false"

steps:
- name: Refuse any ref other than main-v2
if: github.ref != 'refs/heads/main-v2'
run: |
Write-Error "Release builds run only from refs/heads/main-v2."
exit 1

- name: Check out the repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Nerdbank.GitVersioning computes the product version from the full history.
fetch-depth: 0
persist-credentials: false

- name: Install mise
uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
with:
version: ${{ env.MISE_VERSION }}
install: false
# Never restore a cache into a release build.
cache: false

- name: Install the pinned .NET SDK
run: mise install --locked http:dotnet-development

- name: Restore with the committed lock files
run: mise exec -- dotnet restore Windows.slnx --locked-mode

- name: Build
run: mise exec -- dotnet build Windows.slnx --configuration Release --no-restore

- name: Create a fresh scenario temporary directory
run: New-Item -ItemType Directory -Path "${{ runner.temp }}\scenarios"

- name: Run the Windows synthetic scenarios without host-timing assertions
run: >-
mise exec -- dotnet
tests/Authentication.Windows.Scenarios/bin/Release/net10.0-windows/Authentication.Windows.Scenarios.dll
--filter "TestCategory!=WindowsHostTiming"
env:
TEMP: ${{ runner.temp }}\scenarios
TMP: ${{ runner.temp }}\scenarios

- name: Package
run: >-
mise exec -- pwsh -NoProfile -File packaging/windows/package.ps1
-OutputDirectory "${{ runner.temp }}\package"

- name: Record the version and the build chain
id: version
env:
PACKAGE: ${{ runner.temp }}\package
run: |
$zip = @(Get-ChildItem -LiteralPath $env:PACKAGE -Filter 'azureauth-unofficial-*-win-x64.zip')
if ($zip.Count -ne 1) { throw "expected one package zip, found $($zip.Count)" }
if ($zip[0].Name -notmatch '^azureauth-unofficial-(0\.1\.0-alpha\.(0|[1-9][0-9]*))-win-x64\.zip$') {
throw "unexpected package name $($zip[0].Name)"
}
$version = $Matches[1]
$commit = (git rev-parse HEAD).Trim()
$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
# The ILCompiler package finds Visual Studio with vswhere -latest -prerelease; match it.
$vsArgs = @('-latest', '-prerelease', '-products', '*', '-requires', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64')
$vs = & $vswhere @vsArgs -property installationPath
$vsVersion = & $vswhere @vsArgs -property catalog_productDisplayVersion
$msvc = (Get-Content -LiteralPath (Join-Path $vs 'VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt')).Trim()
$link = (Get-Item -LiteralPath (Join-Path $vs "VC\Tools\MSVC\$msvc\bin\Hostx64\x64\link.exe")).VersionInfo.FileVersion
$kits = Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\Lib'
$sdks = @(Get-ChildItem -LiteralPath $kits -Directory |
Where-Object { Test-Path (Join-Path $_.FullName 'um\x64') } |
ForEach-Object { $_.Name } | Sort-Object { [version]$_ })
$info = [ordered]@{
version = $version
commit = $commit
runnerImage = "$env:ImageOS $env:ImageVersion"
dotnetSdk = (mise exec -- dotnet --version).Trim()
visualStudio = $vsVersion
msvc = $msvc
linkExe = $link
windowsSdks = $sdks
}
$json = $info | ConvertTo-Json
Set-Content -LiteralPath (Join-Path $env:PACKAGE 'build-info.json') -Value $json
Write-Host $json
Add-Content -LiteralPath $env:GITHUB_STEP_SUMMARY -Value @('## Build chain', '', '```json', $json, '```')
Add-Content -LiteralPath $env:GITHUB_OUTPUT -Value @("version=$version", "commit=$commit")

- name: Upload the package for the next jobs
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: package
path: ${{ runner.temp }}\package\*
if-no-files-found: error
# The owner accepted at most one day of exposure; nothing deletes them earlier.
retention-days: 1

attest:
name: Attest build provenance
needs: build
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
id-token: write
attestations: write
steps:
- name: Download the package
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: package
path: package

- name: Attest both zips
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: package/*.zip

draft:
name: Create the draft prerelease
needs: [build, attest]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
VERSION: ${{ needs.build.outputs.version }}
COMMIT: ${{ needs.build.outputs.commit }}
steps:
- name: Download the package
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: package
path: package

- name: Verify the checksums and the recorded commit
working-directory: package
run: |
sha256sum --check -- *.sha256
test "$(jq -r .commit build-info.json)" = "$COMMIT"
test "$(jq -r .version build-info.json)" = "$VERSION"

- name: Refuse an existing tag, release or draft
# bash -eo pipefail, so a failed lookup in a pipeline stops the run.
shell: bash
run: |
# Only a 404 means absent; any other failure stops the run.
absent() {
local err
if err=$(gh api "$1" 2>&1 >/dev/null); then return 1; fi
if grep -q 'HTTP 404' <<<"$err"; then return 0; fi
echo "$err" >&2
exit 1
}
absent "repos/$GH_REPO/git/ref/tags/v$VERSION" || { echo "Tag v$VERSION already exists." >&2; exit 1; }
absent "repos/$GH_REPO/releases/tags/v$VERSION" || { echo "A published release v$VERSION already exists." >&2; exit 1; }
drafts=$(gh api --paginate "repos/$GH_REPO/releases" --jq "[.[] | select(.tag_name == \"v$VERSION\")] | length" | awk '{ n += $1 } END { print n + 0 }')
if [ "$drafts" -ne 0 ]; then echo "A release or draft for v$VERSION already exists." >&2; exit 1; fi

- name: Create the draft prerelease
run: |
{
echo "**azureauth-unofficial $VERSION** is an unsupported alpha prerelease of"
echo "[AzureAuth Unofficial V2](https://github.com/$GH_REPO), an independent, unofficial"
echo "fork of Microsoft Authentication CLI. It is not a Microsoft product and has no"
echo "support, production-readiness or compatibility commitment."
echo
echo "- Built from commit $COMMIT by this repository's release workflow. The zips carry"
echo " [GitHub build-provenance attestations](https://github.com/$GH_REPO/attestations)."
echo "- The executables are not code-signed. Compare each zip with its .sha256 file"
echo " before extracting."
echo "- The package bundles the Microsoft-owned Visual Studio public client as an opt-in"
echo " Client Profile. Sign-in, consent and audit records show Visual Studio; Microsoft"
echo " provides no support or availability commitment for it."
echo "- The -symbols.zip holds the native and managed symbol files for diagnosing this"
echo " build; it is not needed to run it."
echo
echo "Read the [package README](https://github.com/$GH_REPO/blob/$COMMIT/packaging/windows/README.md)"
echo "before use. The release review for this prerelease is recorded on"
echo "[Issue #506](https://github.com/$GH_REPO/issues/506)."
} > notes.md
gh release create "v$VERSION" --draft --prerelease --target "$COMMIT" \
--title "azureauth-unofficial $VERSION" --notes-file notes.md \
package/*.zip package/*.sha256
7 changes: 5 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ pwsh packaging/windows/package.ps1
```

The [Windows design](docs/designs/windows-ado-authentication.md#portable-package-and-version)
owns the package content and checks.
owns the package content and checks. Prereleases are built only by the
[release workflow](docs/designs/windows-ado-authentication.md#release-workflow), started by
hand from `main-v2`; the repository owner publishes each draft after its release review.

## .NET and Agent Tooling

Expand All @@ -147,7 +149,8 @@ management waits for a concrete product consumer.

`mise.lock` is generator output: never edit it by hand. Generate it with the mise
version that CI pins, the `MISE_VERSION` value in `.github/workflows/check.yml`;
`mise.toml` sets the same `min_version`. Another mise
`mise.toml` sets the same `min_version`, and `.github/workflows/release.yml` uses the same
`MISE_VERSION`. Another mise
version can rewrite unrelated entries.

To change the SDK, edit its version and each platform's archive URL and `checksum` in
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ Start with:
## Windows Portable Package

The Windows x64 command is `azureauth-unofficial`, built as an unsigned portable zip
that you extract into a directory of your choice. No package has been published yet. The
that you extract into a directory of your choice. Unsupported `0.1.0-alpha` prereleases
may be published on this repository's GitHub Releases page after a release review. The
[package README](packaging/windows/README.md) covers installation, use from Windows and
WSL, the bundled Visual Studio compatibility Profile and its limits, and removal.

Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ is navigation, not an independent authority.
| Design and validation reviewers | Completed synthetic Native AOT publish, upstream native-import, and public Native AOT assessment evidence | [`windows-native-aot.md`](research/experiments/windows-native-aot.md) |
| Validation and design reviewers | Completed Windows Slice selected-account, WSL, and host observation evidence and its public-source basis | [`windows-slice-validation.md`](research/experiments/windows-slice-validation.md) |
| Validation, design, and security reviewers | Completed 2026-10-15 real-host validation method, results, limitations, and deviation dispositions for the `main-v2` Windows product | [`real-host-validation.md`](research/experiments/real-host-validation.md) |
| Experiment operators and validation reviewers | Host test protocol, attempt ledger, and results for the locally built Windows portable package | [`portable-package-host-test.md`](research/experiments/portable-package-host-test.md) |
| Validation, design, and security reviewers | Completed 2026-10-29 host test method, results, limitations, and deviation dispositions for the locally built Windows portable package | [`portable-package-host-test.md`](research/experiments/portable-package-host-test.md) |
| Research and release reviewers | Mutable public facts and their recheck triggers | [`research/rechecks.yaml`](research/rechecks.yaml) |
| Maintainers and upstream-source consumers | Upstream baseline and import policy | [`../UPSTREAM.md`](../UPSTREAM.md) |

Expand Down
38 changes: 33 additions & 5 deletions docs/designs/windows-ado-authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,15 @@ exact upstream loader, its dependencies, and the process search restriction must
validated together on the actual application; an incompatible loader makes the target
unavailable rather than silently weakening search rules or copying private runtime internals.

A product publish must pin the Windows x64 public native build chain
(Visual Studio C++ tools, Windows SDK, and `link.exe`), SDK/runtime inputs, resolved
NuGet graph, and publish properties before execution. Microsoft's documented Windows
prerequisite is Visual Studio 2022 or later with Desktop development with C++ and its default
components; this record does not select a floating installed compiler. Artifact closure must include the native broker and
A product publish must pin the SDK/runtime inputs, resolved NuGet graph, and publish
properties before execution, and must pin the Windows x64 public native build chain
(Visual Studio C++ tools, Windows SDK, and `link.exe`). Microsoft's documented Windows
prerequisite is Visual Studio 2022 or later with Desktop development with C++ and its
default components; this record does not select a floating installed compiler. The one
exception: for `0.1.0-alpha` prereleases the repository owner accepted the floating chain
of the GitHub-hosted runner image instead of pinning it; the
[release workflow](#release-workflow) records the installed chain on every run and the
release review checks it. Artifact closure must include the native broker and
OS prerequisites; an AOT executable does not imply one-file deployment or no OS dependency.
Debug symbols have a separate diagnostic/release treatment and are included when
comparing total development and distribution costs.
Expand Down Expand Up @@ -190,6 +194,30 @@ Version computation needs a full Git clone: a shallow clone fails the build, and
without `.git`, such as a `git archive` copy, does not build. CI therefore checks out
with `fetch-depth: 0`.


### Release Workflow

`.github/workflows/release.yml` builds `0.1.0-alpha` prereleases. It starts only by hand
and, in its `main-v2` copy, refuses any ref other than `refs/heads/main-v2`, so the build
is a public release. A copy of the workflow on another ref is not bound by that guard;
the release review checks that the run and its attestation come from
`.github/workflows/release.yml` at `refs/heads/main-v2` and the built commit.

Its jobs have least privilege. The build job reads contents only. It restores in locked
mode with the checksum-pinned SDK, runs the Windows scenarios excluding
`WindowsHostTiming`, runs `package.ps1` without its development switches, and records in
`build-info.json` and the run summary the runner image, the .NET SDK, and the installed
chain that the Native AOT linker resolves: the Visual Studio that `vswhere -latest
-prerelease` finds, as ILCompiler does, its default MSVC toolset and `link.exe`, and the
installed Windows SDKs. That this is the chain the linker used is an inference. A second
job holds only `id-token: write` and `attestations: write` and creates a GitHub
build-provenance attestation for both zips. A third holds only `contents: write`: it
verifies the checksums and the recorded commit, refuses a version whose tag, release or
draft exists (failing on any lookup error other than not found), and creates a draft
prerelease `v<version>` targeting the built commit, with the package zip, the symbols zip
and their `.sha256` files. The jobs pass files as workflow artifacts kept for one day.
The repository owner publishes each draft after the release review.

## Protocol 1 Command-Line Contract

Invoke `<windows-cli> authenticate` followed by the arguments below. Options and enum
Expand Down
31 changes: 31 additions & 0 deletions docs/governance/controls.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,37 @@ controls:
- ci
failure_mode: A Windows adapter, CLI or Windows scenario change merges without compiling, or with a synthetic Windows scenario failing, because only the platform-neutral projects ran in CI.

- id: windows-release-workflow
state: current
class: mechanical
enforcement: blocking
producer: GitHub Actions Release workflow
maintainer: Control maintainer
consumers:
- Repository owner and the independent release reviewer before a draft prerelease is published
governing_rules:
- docs/governance/governance-system.md#gov-005-controls-implement-policy
- docs/governance/project.md#support-and-release
- docs/product/requirements/quality-build-and-validation.md#v2-req-053-public-build-chain
- docs/designs/windows-ado-authentication.md#release-workflow
invariant: >-
The main-v2 copy of the workflow drafts a 0.1.0-alpha prerelease only for a manually
started run on refs/heads/main-v2 that restored from public package sources in locked mode with the
checksum-pinned SDK, passed every tests/Authentication.Windows.Scenarios scenario
outside the WindowsHostTiming category, passed every package.ps1 check without its
development switches, recorded its installed native build chain, attested both zips, targets
the built commit, and uses a version with no existing tag, release or draft. Each job holds
only the token permissions its step needs. Publication stays the repository owner's
decision after the release review, which also checks that the run and its attestation
come from that workflow at refs/heads/main-v2.
runner: github-platform
implementation:
kind: repository-path
value: .github/workflows/release.yml
execution_points:
- release
failure_mode: A prerelease is drafted from an unreviewed ref, a failing build, an unattested or mismatched asset, or a token broader than the step needs.

- id: record-system-review
state: current
class: procedural-review
Expand Down
9 changes: 6 additions & 3 deletions docs/governance/operational-identities.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,9 @@ selected_v2:
owning_component: packaging/windows/package.ps1
location: >-
Archive file name, with a SHA-256 checksum file named after the archive with a
.sha256 suffix.
.sha256 suffix. A symbols archive azureauth-unofficial-<version>-win-x64-symbols.zip
with its own .sha256 file holds the build's native and managed symbol files and is
published beside it.
version_behavior: >-
<version> is the computed SemVer 2.0 version without build metadata, for example
0.1.0-alpha.42; the archive name changes with each version.
Expand Down Expand Up @@ -210,8 +212,9 @@ selected_v2:
Prerelease assets of this repository's GitHub Releases. The product never checks
for, downloads or applies updates.
version_behavior: >-
Each prerelease archive carries the computed SemVer 2.0 version without build
metadata in its name. The later release entry selects the tag scheme.
Each prerelease is tagged v<version>, where <version> is the computed SemVer 2.0
version without build metadata (for example v0.1.0-alpha.42), on the built commit;
its archives carry the same version in their names.
collision_test: >-
Upstream update channels are the AzureAD/microsoft-authentication-cli releases and
their installers; this channel is a different repository and archive name.
Expand Down
Loading
Loading