8 Commits
Author SHA1 Message Date
james.maesandClaude Fable 5.2 1915ce2f94 docs(release): the documents say what 1.1.1 does, and the package manifests verify the file they name (#199)
Statements the release reviews of 1.1.1 found untrue at this commit, and two
gaps in the release tooling.

- Threat model: the version reviewed; an installed build contacts
  git.kof22.com for updates; the binaries are signed (the accepted risks named
  them unsigned) and T1 with the Scoop part of T2 is what is accepted; T2 and
  T11 say what ADR 0009 changed, and T11 is not marked mitigated as a whole
  while the copy to the release server is manual.
- SECURITY.md names 1.1.x as the supported line.
- README: the beta is a separate app with its own accounts; parallelism is
  under Sync options, after loading; the buttons have the names on screen; the
  app is built unpackaged, with no Developer Mode. CONTRIBUTING the same.
  docs/CLI.md shows a current version.
- RELEASING.md: the check that the server answers from outside comes before
  anything is tagged; a version that moves the updater is rehearsed with two
  prereleases read from that server; the copy to git.kof22.com is a numbered
  step; the packaging change is committed on Gitea; winget is updated by hand
  while the job would submit the GitHub URL; the engine package is numbered by
  its own public surface.
- Update-PackageManifests.ps1 downloads the file the manifests will name and
  compares its SHA-256 with that of the built file it is given, instead of
  asking only whether the URL answers; it writes the Scoop manifest with the
  repository's line endings.
- One vault test stored an entry in the credential namespace the installed
  app uses. It uses two test scopes now (#161).

Closes #183

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-11-11 06:20:47 +00:00
james.maesandClaude Fable 5.2 6696e37049 feat(update): updates and release downloads come from git.kof22.com (#171)
The in-app updater reads the project's releases on its own Gitea server
through Velopack's GiteaSource, anonymously over HTTPS. The Scoop manifest
(homepage, download, version check, auto-update), the Chocolatey package and
the script that regenerates both for a release name the same server.

- ADR 0009 amends ADR 0001. The threat model records where that trust is now
  placed: the updater checks hashes from the same feed and no signature, so
  whoever can publish on the server can deliver an update. T1 is accepted for
  1.1.1, not mitigated.
- Update-PackageManifests.ps1 refuses to write a manifest whose download does
  not answer, so a package cannot be pushed before its release is on the
  server.
- RELEASING.md: what a release on git.kof22.com must contain, the prerelease
  flag, the ten-release window, and where a broken release is withdrawn.
- PackagingManifestTests pin the auto-update URL, the hash URL, the release
  notes link and the version check exactly.

Versions up to 1.1.0 look for updates on GitHub and are updated once by hand.

Closes #170

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-11-11 05:27:59 +00:00
james.maesandClaude Fable 5.1 2c4726e30e feat(release): Authenticode-sign releases with Azure Artifact Signing over OIDC (#96)
## What

Releases are now Authenticode-signed (K22-REL-05) through the standards'
shared workflow, which already carries the signing steps behind `sign`
(`azure/login` v3.1.0 and `azure/artifact-signing-action` v2.0.0, both
SHA-pinned and current; `vpk pack --azureTrustedSignFile`). ADR 0008.

- `release.yml`: `sign: true`; new `verify-signatures` job after
`release` downloads the published Setup.exe, portable zip and CLI zip
and runs `signtool verify /pa /all` + `Get-AuthenticodeSignature` on
every gclo executable, failing on an invalid, untimestamped or
foreign-publisher signature (`TRUSTED_SIGNING_SUBJECT`).
- Authentication is GitHub OIDC: an Entra application
`gclo-release-signing` with a federated credential for
`repo:KofTwentyTwo/gclo:environment:release` and only *Artifact Signing
Certificate Profile Signer* on the certificate profile. No secret, key
or PFX anywhere. The identity is `release`-environment **variables**:
`AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_SUBSCRIPTION_ID`,
`TRUSTED_SIGNING_ENDPOINT`, `TRUSTED_SIGNING_ACCOUNT`,
`TRUSTED_SIGNING_PROFILE`, `TRUSTED_SIGNING_SUBJECT`.
- `.github/scripts/Set-ReleaseSigning.ps1`: idempotent Azure
configuration (reads the existing account and Active profile through
ARM, ensures app + service principal + federated credential +
profile-scoped role, prints the `gh variable set` lines). Run
interactively by the owner; never creates secrets or touches the
account, validation or profile.
- Docs: RELEASING.md ("Code signing", local signing with SignTool + the
`Microsoft.ArtifactSigning.Client` dlib, the verify job), SECURITY.md,
README verify step 4, threat-model revision row.

Closes #57 (part of #62). After the first signed release verifies,
EX-0002 closes for gclo in the standards register.

## Azure configuration status

Pending the owner's interactive sign-in; the script output and `signtool
verify` results of the first signed prerelease will be posted here
before merge.

## How it was tested

- `actionlint` clean; PSScriptAnalyzer 0 findings.
- Verification of a real signed build: pending the Azure configuration
(recorded here when done).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: James Maes <james@kof22.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-10 14:39:26 -05:00
james.maesandClaude Fable 5.1 987762295d ci: release through the standards' shared Velopack workflow; publishers fail instead of skip (#77)
## What

Releases now build through the standards' shared `release-velopack.yml`
(standards#5, pinned to `e8ab30f`), which gives SLSA Build L3 provenance
and lets exception EX-0004 close after the first successful run.
`release.yml` keeps only what is gclo's, and every publisher **fails
instead of skipping** when it cannot publish, so a green run means every
channel shipped (#76, part of #62; blocks #67).

- `release.yml`: `verify` (tag on `main`) → `gates` (the full `ci`
workflow, now including the FlaUI suite as a blocking gate since it is a
required check on `main`; the `ui-tests-advisory` input is removed,
closes #54) → `release` (shared workflow: locked restore, Release build,
tests + 100% coverage gate, publish, vpk pack, Syft SBOMs, `SHA256SUMS`,
provenance, draft-then-publish) → caller-side `packages`
(Scoop/Chocolatey manifests PR + `choco push`), `winget`, and opt-in
`nuget`. Publishers run in the `release` environment (created;
deployment rule: tags `v*`) and fail on a missing `DEPS_PAT`,
`CHOCO_API_KEY`, `WINGET_TOKEN`/`WINGET_PACKAGE_ID`, or `NUGET_API_KEY`
when `NUGET_PUBLISH` is `true`. Separate jobs, so a failed publisher
re-runs alone after its token is added.
- **Lock files:** the locked release publish had never run
(`-p:RestoreLockedMode=true` arrived in #68 after the last tag) and
failed locally: `-r win-x64 --self-contained -p:PublishSingleFile=true`
changes every project's restore graph (RID graph;
`Microsoft.NET.ILLink.Tasks` via the single-file analyzer).
`gclo.Engine`, `gclo.ViewModels` and `gclo.Cli` now declare
`RuntimeIdentifiers=win-x64` and `EnableSingleFileAnalyzer=true`, and
the lock files carry both graphs, so the plain locked restore and the
publish agree.
- Asset names follow the shared workflow:
`gclo-cli-win-x64-<version>.zip`, `gclo-<version>.cdx.json`,
`gclo-cli-<version>.cdx.json`; generated release notes replace
`New-ReleaseNotes.ps1` (deleted). `Update-PackageManifests.ps1`,
`bucket/gclo.json` (autoupdate URL) and `PackagingManifestTests` follow;
the tests accept both names because 1.0.0's manifests still point at the
unversioned asset.
- LICENSE ships inside the published app and CLI through the project
files (the shared workflow does not add it).
- Docs: `docs/RELEASING.md` rewritten (required tokens table, rehearsal
prerelease step, per-job recovery), ADR 0007, README "Verifying a
release" (`--signer-repo KofTwentyTwo/standards`), SECURITY.md,
CONTRIBUTING gate table, ADR 0003, threat model T11 + revision row. #7
lists the tokens to add.

## How it was tested

- `dotnet restore gclo.slnx -p:Platform=x64 --locked-mode` passes;
`dotnet build … -warnaserror` 0 warnings; `gclo.Engine.Tests` 422/422
and `gclo.Cli.Tests` 129/129 with 100% line coverage on all three
packages; `dotnet format --verify-no-changes --severity warn` clean;
PSScriptAnalyzer 0 findings; `actionlint` clean on both workflows.
- The shared workflow's exact publish commands pass locked mode locally
and include `LICENSE`:
  ```powershell
dotnet publish gclo.Cli/gclo.Cli.csproj -c Release -r win-x64
--self-contained -p:RestoreLockedMode=true -p:PublishSingleFile=true
dotnet publish gclo/gclo.csproj -c Release -p:Platform=x64 -r win-x64
--self-contained -p:RestoreLockedMode=true -p:WindowsPackageType=None
-p:WindowsAppSDKSelfContained=true -p:PublishTrimmed=false
  ```
- Not yet exercised on a real tag: the shared workflow has never run
(its author asked for gclo's next prerelease to be its first). Per #67
the rehearsal is `v1.0.1-beta.2`, after the `release` environment holds
the tokens listed on #7.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: James Maes <james@kof22.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 18:03:21 -05:00
james.maesandClaude Fable 5.1 50b3c134eb style: adopt the Kingsrook C# layout and the standard .NET quality gate (#70)
* style: adopt the Kingsrook C# layout and the standard .NET quality gate

The standard .editorconfig and Directory.Build.props from KofTwentyTwo/standards
(3-space indent, Allman braces, `if(` without a space, file-scoped namespaces,
var only when apparent, `s_` static fields, IDE0055 and the Meziantou analyzers
as build errors, NuGetAudit on the whole graph, CI-conditional locked restore),
central package management in Directory.Packages.props, LF line endings via
.gitattributes, and the SPDX header on every C# file. Members are separated by
three blank lines, one top-level type per file (24 types moved into their own
files), ArgumentException parameter names, bounded regex match times, the
announcement event in the EventHandler<AnnouncementEventArgs> shape, and the
CLI sync command split into option parsing, the run, and summary reporting.

Repository-specific adjustments, each justified in .editorconfig: namespaces
keep the product name `gclo`; MA0051 flags only extremes (160 lines / 100
statements); the app project keeps CA1822, IDE0051/52 (XAML-referenced
handlers; dotnet format's fixer would delete them) and IDE0060 (handler
signatures) off; XAML stays at 4 spaces.

PowerShell scripts use the standard PSScriptAnalyzer settings: strict mode,
comment-based help, Allman and 3-space layout, no Write-Host; the NuGet update
script now edits Directory.Packages.props. The CI format gate runs at
--severity warn and PSScriptAnalyzer.

Behavior is unchanged: Engine 422, CLI 129 and FlaUI 11 tests pass, line
coverage stays at 100%.

Closes #65

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>

* ci: analyze PowerShell scripts one file at a time

PSScriptAnalyzer 1.25.0 intermittently fails with "Object reference not set to
an instance of an object" when Invoke-ScriptAnalyzer is handed a directory (it
passed on the same scripts an hour earlier, and locally the same call crashes on
some runs and not others). Handing it one script per call never crashed in
repeated local runs, so the format job now loops over the scripts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>

---------

Signed-off-by: James Maes <james@kof22.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 14:39:43 -05:00
james.maesandClaude Fable 5.1 a562acd55c ci: adopt the standards' pull-request, security, CodeQL and Scorecard gates (#68)
* ci: adopt the standards' pull-request, security, CodeQL and Scorecard gates

Call the KofTwentyTwo/standards reusable workflows (pinned to 855cf44):
pr-gates (Conventional Commit title, DCO, dependency review), security
(gitleaks over history, Trivy + OSV-Scanner on the lock files, zizmor +
actionlint), codeql (security-extended for csharp and actions, gated at
high), plus the Scorecard workflow. Root scanner configs, global.json,
.pre-commit-config.yaml with the dotnet-format hook, and the standard
PSScriptAnalyzer settings come with them.

Harden the existing workflows: persist-credentials false on every checkout,
no restore cache in the release job, verify-job outputs and inputs read from
environment variables instead of ${{ }} inside run:, concurrency on release,
locked restores for publish and pack (the CLI now declares win-x64 so its
lock file carries that runtime graph).

Release assets: a CycloneDX 1.6 SBOM per artifact (attached and attested),
SHA256SUMS (renamed; the channel feed is listed too), LICENSE inside the CLI
zip and the app payload, and release notes generated from Conventional
Commit subjects by .github/scripts/New-ReleaseNotes.ps1 with breaking and
security changes first. Generated pull requests use Conventional Commit
titles; Dependabot gets the 7-day cooldown and the ci prefix; the NuGet
update script holds back versions younger than the cooldown.

The old job names and dependency-review.yml stay until protect-main lists
the new required contexts (#66).

Closes #63

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>

* ci: pin the standards workflows to the commit that exempts file-licensed packages

standards#4 (merged as ae174f5) adds the `allow-dependencies-licenses` input to
the reusable pull-request workflow, fixes the DCO check for commits whose author
has no GitHub login, and accepts `security` titles. The three callers move to
that commit, and pr-gates.yml exempts LibGit2Sharp, Microsoft.WindowsAppSDK and
Microsoft.Windows.SDK.BuildTools from the license check (standards EX-0003):
their nuspecs name a license file, so the dependency graph reports NOASSERTION.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>

---------

Signed-off-by: James Maes <james@kof22.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 14:27:45 -05:00
James MaesandClaude Fable 5.1 2d64ac475a Packaging: Scoop bucket, Chocolatey package, SHA256SUMS.txt (#9)
- bucket/gclo.json: this repo is its own Scoop bucket; checkver github,
  autoupdate keyed on SHA256SUMS.txt. Verified locally with scoop install.
- packaging/chocolatey: nuspec + Install-ChocolateyZipPackage script for the
  same gclo-cli-win-x64.zip asset.
- .github/scripts/Update-PackageManifests.ps1: single source of truth that
  re-points both manifests at a stable release (refuses prereleases).
- release.yml: write + attest + attach SHA256SUMS.txt on every release; on
  stable tags update the manifests, choco pack, choco push when CHOCO_API_KEY
  is set, and open a PR against dev with the updated manifests.
- gclo.Cli.Tests/PackagingManifestTests.cs: both manifests valid, stable, and
  describing the same asset.
- docs/RELEASING.md + README: install and secret docs.

Closes #9

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 07:53:23 -05:00
James MaesandClaude Fable 5.1 c37e411c0e CI: replace Dependabot's NuGet updates with a workflow that can see WinUI TFMs
Dependabot's NuGet updater cannot evaluate Windows-versioned target frameworks
(dependabot/dependabot-core#13923, open since January). On this repo it logged
every newer package as "not compatible. Incompatible project frameworks:
net10.0-windows10.0.19041", and because it updates a package consistently
across projects, the same packages were blocked in the plain net10.0 test
projects too. Net effect: WindowsAppSDK, Velopack, xunit.runner.visualstudio
and Microsoft.NET.Test.Sdk were never offered, and the one PR it did open was
closed by the bot itself.

- .github/scripts/Update-NuGetPackages.ps1 drives the bump from
  `dotnet list package --outdated --format json` (real MSBuild/NuGet
  evaluation): applies minor/patch bumps by exact textual edit of each
  PackageReference (encoding and line endings preserved), holds major bumps
  and prereleases for review, and writes a Markdown report. Verified on a
  copy of the repo with four packages downgraded: three minor/patch bumps
  applied, the major held, and the edited csproj byte-identical to HEAD.
- .github/workflows/nuget-updates.yml runs it weekly (Mondays 06:00 ET) or on
  demand (with an include-major switch), runs the build/test/format gates
  against the bumped tree, and opens or refreshes one PR on deps/nuget-weekly
  against dev with the report and gate results in the body. The third-party
  create-pull-request action is pinned to a commit SHA. With GITHUB_TOKEN the
  PR gets no checks of its own (GitHub does not chain workflows from it); an
  optional DEPS_PAT secret enables that.
- dependabot.yml keeps only the github-actions ecosystem and explains why.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 05:58:15 -05:00