Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
6d1779e
phase 0: stabilize CI runtime and security gates
ajbergh Jul 18, 2026
f9dbd15
phase 0: align release Node runtime with CI
ajbergh Jul 18, 2026
6c16fa5
phase 1: add campaign timing and explicit outcome semantics
ajbergh Jul 18, 2026
f6f3bf9
phase 2: carry draft-only delivery mode through renderer
ajbergh Jul 18, 2026
5fe67d6
phase 1: make retries preflight fresh and preserve run timing
ajbergh Jul 18, 2026
1c5fd4d
phase 1: fix runner imports
ajbergh Jul 18, 2026
5d41d0e
phase 2: harden Outlook COM lifecycle and draft delivery
ajbergh Jul 18, 2026
4e37866
phase 2: classify fake sender failures for runner tests
ajbergh Jul 18, 2026
e9a825e
phase 9: expand campaign retry and state regression tests
ajbergh Jul 18, 2026
88429f8
phase 3: expand persisted campaign snapshot schema
ajbergh Jul 18, 2026
3034a82
phase 3: add atomic create and update campaign storage
ajbergh Jul 18, 2026
0d20ea1
phase 6: cache attachment metadata during rendering
ajbergh Jul 18, 2026
f33cca4
phase 9: verify attachment stat caching
ajbergh Jul 18, 2026
366d22d
phase 0: capture npm ci diagnostics on failure
ajbergh Jul 18, 2026
c26939b
docs: add phase-by-phase remediation status
ajbergh Jul 18, 2026
87ffb86
phase 0: generate repaired npm lockfile artifact
ajbergh Jul 18, 2026
6ff0473
phase 0: repair npm lockfile from deterministic CI environment
ajbergh Jul 18, 2026
1897ccf
phase 0: synchronize frontend package lock
github-actions[bot] Jul 18, 2026
1ea99b5
phase 0: remove temporary lockfile repair permissions
ajbergh Jul 18, 2026
12116cc
phase 0: migrate golangci-lint configuration to v2
ajbergh Jul 18, 2026
2362f6d
phase 10: add coverage, SBOMs, and actionable security diagnostics
ajbergh Jul 18, 2026
cd80edc
chore: add temporary remediation source snapshot
ajbergh Jul 18, 2026
285fa75
phase 10: harden tagged releases with SBOMs, signing hooks, and manif…
ajbergh Jul 18, 2026
c93348d
phase 10: add manual release candidate validation workflow
ajbergh Jul 18, 2026
94a84b1
chore: stage remediation patch part 1
ajbergh Jul 18, 2026
854bd95
chore: stage readable remediation patch 1 of 6
ajbergh Jul 18, 2026
7a14d63
chore: stage readable remediation patch 3 of 6
ajbergh Jul 18, 2026
fee80c7
chore: stage readable remediation patch 4 of 6
ajbergh Jul 18, 2026
5150521
chore: stage readable remediation patch 5 of 6
ajbergh Jul 18, 2026
9213273
chore: stage readable remediation patch 6 of 6
ajbergh Jul 18, 2026
a198d84
chore: apply staged application remediation
ajbergh Jul 18, 2026
9e7ffa3
phases 1-5: persist campaign lineage and complete send workflow
github-actions[bot] Jul 18, 2026
bab0d8e
chore: finalize least-privilege CI and test bound app APIs
ajbergh Jul 18, 2026
c601cd7
fix(frontend): schedule nullability correction
ajbergh Jul 18, 2026
50cd930
fix(frontend): narrow persisted retry fallback request
github-actions[bot] Jul 18, 2026
b1d07e8
docs: add release go-no-go checklist
ajbergh Jul 18, 2026
748344e
phase 10: align release candidate validation with platform boundaries
ajbergh Jul 18, 2026
ef58b65
docs: update remediation status for integrated release hardening
ajbergh Jul 18, 2026
51f0b8f
docs: record release hardening changes
ajbergh Jul 18, 2026
66c6c4a
docs: align contributor checks with hardened CI
ajbergh Jul 18, 2026
6ec306d
chore: capture frontend production build diagnostics
ajbergh Jul 18, 2026
09d805d
ci: preserve compiler and linter diagnostics while enforcing gates
ajbergh Jul 18, 2026
58a5cb7
chore: remove temporary frontend diagnostic workflow
ajbergh Jul 18, 2026
c0bc80e
fix(frontend): accept children in error boundary props
ajbergh Jul 18, 2026
cd9b3e4
chore: stage exact lint remediations
ajbergh Jul 18, 2026
f845363
fix(go): schedule exact lint remediation
ajbergh Jul 18, 2026
ad905c8
chore: remove temporary lint remediation workflow
ajbergh Jul 18, 2026
fb02e7d
fix(go): handle atomic temp-file close errors
ajbergh Jul 18, 2026
4ebb31c
fix(go): narrow staged lint patch to remaining findings
ajbergh Jul 18, 2026
9653a61
fix(go): register remaining lint remediation
ajbergh Jul 18, 2026
b8eab50
docs: document release evidence artifacts
ajbergh Jul 18, 2026
e4825e8
fix(go): apply exact lint remediation through trusted CI
ajbergh Jul 18, 2026
057f76d
fix(go): replace malformed lint patch with verified unified diff
ajbergh Jul 18, 2026
175943b
fix(go): resolve remaining lint findings without suppressions
github-actions[bot] Jul 18, 2026
954639c
fix(go): apply lint remediations with assertion-checked replacements
ajbergh Jul 18, 2026
d6a8eb1
ci: remove completed remediation job and restore least privilege
ajbergh Jul 18, 2026
641bcc7
ci: preserve Windows Wails compiler diagnostics
ajbergh Jul 18, 2026
b6bb5dc
fix(storage): persist draft-only campaign mode
ajbergh Jul 18, 2026
f62f5f7
test(storage): cover draft-only campaign round trip
ajbergh Jul 18, 2026
55c0f47
ci: preserve Windows root-package test diagnostics
ajbergh Jul 18, 2026
4d92944
fix(app): isolate Wails progress emission from run context
ajbergh Jul 18, 2026
7188733
fix(app): apply idempotent Wails progress isolation
ajbergh Jul 18, 2026
d17240f
chore: stage Wails progress-context remediation script
ajbergh Jul 18, 2026
da10f25
fix(app): run validated progress-context remediation script
ajbergh Jul 18, 2026
807bede
fix(app): separate source remediation from workflow cleanup
ajbergh Jul 18, 2026
96b6a12
fix(app): isolate Wails progress emission from run context
github-actions[bot] Jul 18, 2026
19cdd97
test(app): cover headless progress handling and context fallback
ajbergh Jul 18, 2026
ef6eff6
ci: remove completed one-time remediation job
ajbergh Jul 18, 2026
46f695f
docs: record green automated release baseline
ajbergh Jul 18, 2026
0d0a101
docs: refresh release and workflow documentation
ajbergh Jul 21, 2026
6d31f39
docs: consolidate unreleased changelog
ajbergh Jul 21, 2026
4e5da72
docs: finalize remediation phase status
ajbergh Jul 21, 2026
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
152 changes: 138 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ on:
# Pin tool versions in one place for reproducibility.
env:
GO_VERSION: "1.24.x"
NODE_VERSION: "20"
NODE_VERSION: "22.23.1"
WAILS_VERSION: "v2.11.0"
GOLANGCI_LINT_VERSION: "v2.11.4"
CYCLONEDX_GOMOD_VERSION: "v1.10.0"

permissions:
contents: read
Expand All @@ -28,20 +30,45 @@ jobs:
run: |
unformatted=$(gofmt -l $(git ls-files '*.go'))
if [ -n "$unformatted" ]; then
echo "These files are not gofmt-clean:"; echo "$unformatted"; exit 1
echo "These files are not gofmt-clean:"
echo "$unformatted"
exit 1
fi
- name: go vet
run: go vet ./backend/...
- name: go test (race)
run: go test -race ./backend/...
- name: go test (race + coverage)
run: go test -race -covermode=atomic -coverprofile=backend-coverage.out ./backend/...
- name: Upload backend coverage
if: always()
uses: actions/upload-artifact@v4
with:
name: backend-coverage
path: backend-coverage.out
if-no-files-found: ignore
retention-days: 14
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
id: golangci
continue-on-error: true
uses: golangci/golangci-lint-action@v9
with:
version: ${{ env.GOLANGCI_LINT_VERSION }}
args: --output.text.path=golangci-lint.txt ./backend/...
- name: Upload golangci-lint diagnostics
if: always()
uses: actions/upload-artifact@v4
with:
version: v1.61.0
args: ./backend/...
name: golangci-lint-diagnostics
path: golangci-lint.txt
if-no-files-found: ignore
retention-days: 7
- name: Enforce golangci-lint gate
if: steps.golangci.outcome == 'failure'
run: |
cat golangci-lint.txt 2>/dev/null || true
exit 1

frontend:
name: Frontend (lint, test, build)
name: Frontend (test, coverage, build)
runs-on: ubuntu-latest
defaults:
run:
Expand All @@ -55,8 +82,29 @@ jobs:
cache-dependency-path: frontend/package-lock.json
- run: npm ci
- run: npm run lint --if-present
- run: npm run test --if-present
- run: npm run build
- name: Frontend tests with coverage
run: npm run test -- --coverage
- name: Upload frontend coverage
if: always()
uses: actions/upload-artifact@v4
with:
name: frontend-coverage
path: frontend/coverage
if-no-files-found: ignore
retention-days: 14
- name: Frontend production build
shell: bash
run: |
set -o pipefail
npm run build 2>&1 | tee ../frontend-build.log
- name: Upload frontend build diagnostics
if: always()
uses: actions/upload-artifact@v4
with:
name: frontend-build-diagnostics
path: frontend-build.log
if-no-files-found: ignore
retention-days: 7

windows-build:
name: Wails Windows build
Expand All @@ -78,13 +126,38 @@ jobs:
working-directory: frontend
run: npm ci
- name: Build (unsigned)
run: wails build -platform windows/amd64 -skipbindings -ldflags "-X main.Version=ci-${{ github.sha }} -X main.Commit=${{ github.sha }}"
shell: pwsh
run: |
wails build -platform windows/amd64 -skipbindings -ldflags "-X main.Version=ci-${{ github.sha }} -X main.Commit=${{ github.sha }}" 2>&1 | Tee-Object -FilePath wails-build.log
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Upload Wails build diagnostics
if: always()
uses: actions/upload-artifact@v4
with:
name: wails-windows-build-diagnostics
path: wails-build.log
if-no-files-found: ignore
retention-days: 7
- name: Root package tests
shell: pwsh
run: |
go test . 2>&1 | Tee-Object -FilePath root-package-tests.log
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Upload root package test diagnostics
if: always()
uses: actions/upload-artifact@v4
with:
name: root-package-test-diagnostics
path: root-package-tests.log
if-no-files-found: ignore
retention-days: 7
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: MailMergeApp-windows-unsigned
path: build/bin/*.exe
if-no-files-found: error
retention-days: 14

security:
name: Dependency scan
Expand All @@ -95,12 +168,63 @@ jobs:
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: govulncheck
shell: bash
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./backend/... || true
- name: npm audit
set +e
govulncheck -json ./backend/... > govulncheck.json
status=$?
cat govulncheck.json
exit $status
- name: Upload govulncheck report
if: always()
uses: actions/upload-artifact@v4
with:
name: govulncheck-report
path: govulncheck.json
if-no-files-found: ignore
retention-days: 14
- name: npm production audit
working-directory: frontend
run: |
npm ci
npm audit --omit=dev || true
npm audit --omit=dev --audit-level=high

sbom:
name: Generate SBOMs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Generate Go CycloneDX SBOM
run: |
go install github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@${{ env.CYCLONEDX_GOMOD_VERSION }}
cyclonedx-gomod mod -json -output backend.cdx.json
- name: Generate frontend CycloneDX SBOM
working-directory: frontend
run: |
npm ci
npm sbom --omit=dev --sbom-format=cyclonedx > ../frontend.cdx.json
- name: Upload SBOMs
uses: actions/upload-artifact@v4
with:
name: cyclonedx-sboms
path: |
backend.cdx.json
frontend.cdx.json
if-no-files-found: error
retention-days: 30
123 changes: 123 additions & 0 deletions .github/workflows/release-candidate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
name: Release Candidate

on:
workflow_dispatch:
inputs:
label:
description: Optional release-candidate label, for example 1.2.0-rc.1
required: false
type: string

permissions:
contents: read

env:
GO_VERSION: "1.24.x"
NODE_VERSION: "22.23.1"
WAILS_VERSION: "v2.11.0"
GOLANGCI_LINT_VERSION: "v2.11.4"
CYCLONEDX_GOMOD_VERSION: "v1.10.0"

jobs:
validate:
name: Validate release candidate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Go formatting
shell: bash
run: |
unformatted=$(gofmt -l $(git ls-files '*.go'))
test -z "$unformatted" || { echo "$unformatted"; exit 1; }
- name: Go vet
run: go vet ./backend/...
- name: Go race tests and coverage
run: go test -race -covermode=atomic -coverprofile=backend-coverage.out ./backend/...
- name: Go lint
uses: golangci/golangci-lint-action@v9
with:
version: ${{ env.GOLANGCI_LINT_VERSION }}
args: ./backend/...
- name: Go vulnerability scan
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./backend/...
- name: Frontend install, test, and build
working-directory: frontend
run: |
npm ci
npm run lint --if-present
npm run test -- --coverage
npm run build
npm audit --omit=dev --audit-level=high
- name: Generate CycloneDX SBOMs
run: |
go install github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@${{ env.CYCLONEDX_GOMOD_VERSION }}
cyclonedx-gomod mod -json -output backend.cdx.json
cd frontend
npm sbom --omit=dev --sbom-format=cyclonedx > ../frontend.cdx.json
- name: Upload validation evidence
uses: actions/upload-artifact@v4
with:
name: release-candidate-validation
path: |
backend-coverage.out
frontend/coverage
backend.cdx.json
frontend.cdx.json
if-no-files-found: error
retention-days: 30

windows-package:
name: Build Windows release candidate
needs: validate
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install Wails CLI
run: go install github.com/wailsapp/wails/v2/cmd/wails@${{ env.WAILS_VERSION }}
- name: Install frontend dependencies
working-directory: frontend
run: npm ci
- name: Build release candidate
shell: pwsh
run: |
$label = "${{ inputs.label }}"
if ([string]::IsNullOrWhiteSpace($label)) { $label = "rc-${{ github.run_number }}" }
$date = (Get-Date -Format "yyyy-MM-dd")
wails build -platform windows/amd64 -ldflags "-X main.Version=$label -X main.Commit=${{ github.sha }} -X main.BuildDate=$date"
- name: Root package tests
run: go test .
- name: Create checksums
shell: pwsh
run: |
Get-ChildItem build/bin/*.exe | Sort-Object Name | ForEach-Object {
(Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant() + " " + $_.Name
} | Out-File build/bin/SHA256SUMS.txt -Encoding ascii
- name: Upload release candidate
uses: actions/upload-artifact@v4
with:
name: MailMergeApp-release-candidate
path: |
build/bin/*.exe
build/bin/SHA256SUMS.txt
if-no-files-found: error
retention-days: 30
Loading
Loading