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
83 changes: 83 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
version: 2.1


orbs:
win: circleci/windows@5.0

Expand Down Expand Up @@ -118,6 +119,78 @@ jobs:
& powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $publisher -AssetsDir (Join-Path $root 'release-output') -Tag $env:CIRCLE_TAG
if ($LASTEXITCODE -ne 0) { throw "publish-github-release.ps1 failed with exit code $LASTEXITCODE" }


pr-check:
executor:
name: win/default
size: medium
environment:
CARGO_TERM_COLOR: always
# Stable relative target dir so the target cache below is portable and
# every cargo invocation in local-check.ps1 shares one cache.
CARGO_TARGET_DIR: target
# Compile-time pipeline values are exported as environment variables,
# never passed as command-line arguments: an empty-string argument does
# not survive the Windows argv handoff to a child powershell.exe -File
# invocation (MissingArgument). gates.ps1 reads CBX_PR_URL /
# CBX_PR_BASE_SHA, plus CI_BUDGET_MODE / CIRCLE_BRANCH / CIRCLE_SHA1,
# from the environment with [AllowEmptyString] defaults.
CBX_PR_URL: << pipeline.event.context.github.pr_url >>
CBX_PR_BASE_SHA: << pipeline.event.github.pull_request.base.sha >>
steps:
- checkout
- run:
name: Budget, trigger, and docs-only gates
shell: powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass
command: |
$ErrorActionPreference = 'Stop'
& powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File (Join-Path (Get-Location).Path 'scripts\circleci-pr-gates.ps1')
if ($LASTEXITCODE -ne 0) { throw "circleci-pr-gates.ps1 failed with exit code $LASTEXITCODE" }
- restore_cache:
name: Restore Cargo registry and git cache
keys:
- pr-check-cargo-{{ checksum "Cargo.lock" }}
- pr-check-cargo-
- restore_cache:
name: Restore pnpm store cache
keys:
- pr-check-pnpm-{{ checksum "apps/desktop-tauri/pnpm-lock.yaml" }}
- pr-check-pnpm-
- restore_cache:
name: Restore Cargo target cache (exact or older same-Rust fallback)
keys:
- pr-check-target-v1-{{ checksum "scripts/circleci-pinned-rust.txt" }}-{{ checksum "Cargo.lock" }}-{{ checksum "Cargo.toml" }}-{{ checksum "rust/Cargo.toml" }}-{{ checksum "apps/desktop-tauri/src-tauri/Cargo.toml" }}
- pr-check-target-v1-{{ checksum "scripts/circleci-pinned-rust.txt" }}-{{ checksum "Cargo.lock" }}-
- run:
name: Provision pinned toolchain and run local-check ci slice
shell: powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass
no_output_timeout: 60m
command: |
$ErrorActionPreference = 'Stop'
$pinnedRust = (Get-Content -Raw -LiteralPath 'scripts\circleci-pinned-rust.txt').Trim()
& powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File (Join-Path (Get-Location).Path 'scripts\run-circleci-pr-check.ps1') `
-RustVersion $pinnedRust `
-NodeVersion '24.18.0' `
-NodeMajor 24
if ($LASTEXITCODE -ne 0) { throw "run-circleci-pr-check.ps1 failed with exit code $LASTEXITCODE" }
- save_cache:
name: Save Cargo registry and git cache
key: pr-check-cargo-{{ checksum "Cargo.lock" }}
paths:
- ~/.cargo/registry
- ~/.cargo/git
- save_cache:
name: Save pnpm store cache
key: pr-check-pnpm-{{ checksum "apps/desktop-tauri/pnpm-lock.yaml" }}
paths:
- ~/AppData/Local/pnpm/store
- save_cache:
name: Save Cargo target cache
key: pr-check-target-v1-{{ checksum "scripts/circleci-pinned-rust.txt" }}-{{ checksum "Cargo.lock" }}-{{ checksum "Cargo.toml" }}-{{ checksum "rust/Cargo.toml" }}-{{ checksum "apps/desktop-tauri/src-tauri/Cargo.toml" }}
paths:
- target


workflows:
release:
jobs:
Expand All @@ -137,3 +210,13 @@ workflows:
requires:
- release-approval
filters: *release-tag-filter

pr-check:
jobs:
- pr-check:
filters:
tags:
ignore:
- /^v[0-9]+\.[0-9]+\.[0-9]+$/
branches:
only: /.*/
77 changes: 41 additions & 36 deletions .github/CI.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,28 @@

Win-CodexBar has two deliberately separate hosted CI responsibilities:

- **Blacksmith GitHub Actions** remains the primary PR/push validation path.
- **CircleCI** is a release-only Windows path. It can start only for a
canonical protected semver tag (`vX.Y.Z`), never for a branch or PR.
- **CircleCI** hosts the primary PR/push validation path: the `pr-check`
job/workflow in `.circleci/config.yml` runs the full local-check slice on
CircleCI's hosted Windows executor for every branch pipeline that passes
its gates.
- The **Blacksmith GitHub Actions** PR check
(`.github/workflows/pr-check.yml`) is a manual-dispatch-only fallback: its
`on:` block holds `workflow_dispatch` only, so it no longer schedules
automatically and is run by hand only for Blacksmith diagnostics.

The CircleCI pipeline does not replace or weaken the Blacksmith checks. Its
build job has no GitHub write credential; only the post-approval publisher
receives the restricted `GH_TOKEN` context.
These responsibilities do not overlap: the CircleCI PR check replaces the
former Blacksmith PR/push gate (see ADR 0005). Its build job has no GitHub
write credential; only the post-approval release publisher receives the
restricted `GH_TOKEN` context.

## Blacksmith GitHub Actions
## CircleCI hosted PR check (primary PR/push gate)

### PR check — `.github/workflows/pr-check.yml`
### Workflow — `.circleci/config.yml`

Runs on `pull_request`, on `push` to `main`/`master`, and on
`workflow_dispatch`. Runner: `blacksmith-4vcpu-windows-2025`
(Windows Server 2025; VS Build Tools available per Blacksmith docs).

Exact commands run, in order:
The hosted PR/push validation gate now runs on **CircleCI** as the
`pr-check` job in the `pr-check` workflow (project `nesszer/Win-CodexBar`),
not on Blacksmith. The CircleCI job delegates the whole check to
`scripts/local-check.ps1 -Slice ci`, so the exact commands it runs are:

```powershell
cargo fmt --all --check
Expand All @@ -28,11 +33,9 @@ pnpm --dir apps/desktop-tauri test
pnpm --dir apps/desktop-tauri run build
```

This is the **local-check slice** from `scripts/local-check.ps1` only. It does
not run release packaging, installer smoke, or publication.

`concurrency.cancel-in-progress` is on, keyed by ref, so superseded pushes
cancel the in-flight run.
Auto-cancel of superseded pushes is a CircleCI **project setting** ("Auto-cancel
redundant workflows", Project Settings → Advanced), not GitHub `concurrency`
YAML — the CircleCI job has none.

### Interaction guard — `.github/workflows/interaction-guard.yml`

Expand All @@ -42,22 +45,24 @@ permissions. It is unrelated to release publication.

## GitHub Actions budget mode

Both GitHub workflows carry `if: vars.CI_BUDGET_MODE != 'off'`, so they run
when the variable is unset (`normal`), `normal`, or `thin`, and skip only when
it is `off`. This gate does not disable CircleCI releases.
Only the interaction guard carries the `if: vars.CI_BUDGET_MODE != 'off'`
gate now; it runs when the variable is unset (`normal`), `normal`, or
`thin`, and skips only when it is `off`. This gate does not disable
CircleCI releases.

Set `CI_BUDGET_MODE` in **Settings → Secrets and variables → Actions →
Variables**. Do not hard-code it in a workflow.

| Mode | PR check | Interaction guard | Circle release |
|--------|----------|-------------------|----------------|
| normal | runs | runs | tag-triggered |
| thin | runs | runs | tag-triggered |
| off | skip | skip | tag-triggered |
| Mode | Interaction guard | Circle release |
|--------|-------------------|----------------|
| normal | runs | tag-triggered |
| thin | runs | tag-triggered |
| off | skip | tag-triggered |

The Blacksmith Pool minutes intent remains roughly **60% Win-CodexBar**,
**30% linear-cli**, and **10% buffer**. CircleCI credits are separate and must
be budgeted in CircleCI.
The Blacksmith Pool minutes intent was roughly **60% Win-CodexBar**,
**30% linear-cli**, and **10% buffer**; that allocation is historical for this
repo — Win-CodexBar no longer draws on the pool. CircleCI credits are separate
and must be budgeted in CircleCI.

## CircleCI release pipeline

Expand Down Expand Up @@ -101,7 +106,8 @@ automated by this repository:
permission; no workflow file is changed by the publisher.
4. Protect the `v*` tag namespace with a GitHub ruleset/tag protection policy
that permits only authorized release maintainers to create canonical
`vX.Y.Z` tags. Protect `main` and require the normal Blacksmith checks.
`vX.Y.Z` tags. Protect `main` and require the `ci/circleci: pr-check`
status check.
5. Configure CircleCI notifications and a spending/credit alert appropriate to
the organization. Do not approve a release until the build artifacts and
manifest have been reviewed.
Expand All @@ -112,12 +118,11 @@ CircleCI project setup and GitHub tag/context/ruleset changes are the current
manual setup scope.

## Cost, retry, and rollback behavior

Blacksmith Windows minutes remain the recurring PR cost and are still billed
according to the existing Blacksmith plan (Windows has historically billed at
2x on its free tier). CircleCI release builds add Windows executor credits only
for protected semver tags, plus the short approval/publish job. Do not use
CircleCI for branch validation or ad-hoc release testing.
CircleCI Windows credits are now the recurring PR cost (spent against the
open-source allowance; see ADR 0005), plus release builds for protected
semver tags and the short approval/publish job. Blacksmith minutes remain
relevant only for the interaction guard and manual-dispatch diagnostics.
Do not use CircleCI for ad-hoc release testing outside its gated triggers.

Reruns are safe: the build is tied to the immutable SHA from the tag and
produces a fresh temporary WorkRoot. If publication stops after some uploads,
Expand Down
43 changes: 9 additions & 34 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
name: PR check

# Blacksmith CI only: PR validation. Release assets stay in CircleCI.
# Manual-only historical fallback: the hosted PR check moved to CircleCI
# (ADR 0005). This workflow no longer schedules on push or pull_request;
# run it by hand for Blacksmith diagnostics. The job body is kept intact
# for that purpose.
on:
push:
branches: [main, master]
paths-ignore:
- 'docs/**'
- '**/*.md'
- 'CONTEXT.md'
- '.github/CI.md'
pull_request:
paths-ignore:
- 'docs/**'
- '**/*.md'
- 'CONTEXT.md'
- '.github/CI.md'
workflow_dispatch:

concurrency:
Expand Down Expand Up @@ -60,23 +50,8 @@ jobs:
cache: pnpm
cache-dependency-path: apps/desktop-tauri/pnpm-lock.yaml

- name: Install frontend deps
run: pnpm --dir apps/desktop-tauri install --frozen-lockfile

- name: Rust format check
run: cargo fmt --all --check

- name: Rust clippy (workspace)
run: cargo clippy --workspace --all-targets -- -D warnings

- name: Rust tests (workspace)
run: cargo test --workspace

- name: Frontend tests
run: pnpm --dir apps/desktop-tauri test

- name: Frontend type check / build
run: pnpm --dir apps/desktop-tauri run build

- name: Interaction guard script tests
run: node --test .github/scripts/interaction-guard.test.mjs
# Canonical check source of truth: the same slice the CircleCI
# pr-check job runs (fmt/clippy/test, frontend install/test/build,
# interaction-guard script tests). No duplicated inline steps here.
- name: Run local-check ci slice
run: powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File scripts/local-check.ps1 -Slice ci
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ nul
.env
.env.*
/.local/
/docs/
# Docs children ignored generally; ADRs are product docs and stay trackable
/docs/*
!/docs/adr/
/docs/adr/*
!/docs/adr/*.md
/scripts/*
/docs/ui-parity-workflow.md
/scripts/crop_vm_preferences_proof.sh
Expand Down
Loading