Skip to content
Open
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
58 changes: 58 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,61 @@ jobs:
jr-*.tar.gz
jr-*.zip
jr-*.sha256

attest:
name: Attest build provenance
needs: build
# Fork opt-in gate, matching this repo's release-ops convention
# (SIGNING_ENABLED, HOMEBREW_TAP_REPO, RELEASE_GAP_FILL_ENABLED,
# SYNC_UPSTREAM_REPO — see docs/specs/fork-friendly-release-ops.md).
# Forks carry this file verbatim as a no-op unless they opt in.
# Attestations require `id-token: write` and publish the workflow's
# commit SHA to the public Rekor transparency log; they are
# unavailable on GHES and on private forks without GitHub Enterprise
# Cloud. Gating keeps the file byte-identical downstream while never
# hard-failing a fork's release.
if: vars.ATTESTATIONS_ENABLED == 'true'
# Runs in PARALLEL with `release` (both `needs: build`), not upstream
# of it. Both fan in from the same immutable `build` artifacts, so the
# attested bytes are identical to what `release` uploads verbatim
# (softprops copies them unchanged) — the attestation is created from
# the built bytes, not re-downloaded from the release page, so there is
# no publish-then-attest window (TOCTOU / CWE-362). It is deliberately
# NOT a `needs:` of `release`: with the fork gate above, a skipped
# `attest` would otherwise cascade and skip the fork's entire release.
permissions:
# attest-build-provenance needs exactly these two; neither is
# inherited. contents: read is NOT needed — the artifact download
# goes through the Actions artifacts API, not the repo contents API.
id-token: write
attestations: write
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit

- name: Fan in the built artifacts (same objects `release` uploads)
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
merge-multiple: true
path: release-assets

# Attest the archives only. The `.sha256` sidecars are integrity
# metadata already covered by the archive attestation; attesting
# them would be circular.
#
# Coverage boundary: this attests the bytes produced by release.yml.
# The fork-optional `sign-and-publish.yml` re-signs the darwin
# binaries and emits `.pkg`/`.dmg` with DIFFERENT digests after
# notarization — those are not covered here and need their own attest
# step in that workflow (after codesign/notarize, before upload),
# gated on the same variable. Out of scope for this PR.
- name: Attest build provenance
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: |
release-assets/*.tar.gz
release-assets/*.zip
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to jr will be documented here.

## [Unreleased]

### Added

- **CI: build provenance attestations for release artifacts (opt-in):** `release.yml` gains an `attest` job that produces a GitHub Artifact Attestation with SLSA build provenance for the `.tar.gz`/`.zip` release archives, verifiable with `gh attestation verify` and natively by mise's `github:` backend. Attestation is created from the built artifacts (parallel to `release`, fanning in from the same `build` job outputs), so it covers the exact bytes users download without a publish-then-attest window. Gated on `vars.ATTESTATIONS_ENABLED` so forks carry it as a no-op. See `docs/specs/fork-friendly-release-ops.md`.

## [0.6.0-dev.10] - 2026-07-15

### Breaking Changes
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,14 +330,14 @@ When adding a new feature:
- `JR_CONFIG_DIR` env var overrides the config directory in debug builds (cross-platform test isolation seam; see BC-6.2.017). Debug builds only — release binaries ignore this env var. The override is read before the `#[cfg(windows)]` / `#[cfg(not(windows))]` OS split in `src/config.rs::global_config_dir()`, so it works identically on all platforms. Pinned by `tests/config_dir_release_gate.rs`.
- `JR_CACHE_DIR` env var overrides the cache root directory in debug builds (cross-platform test isolation seam; see BC-6.2.017). Debug builds only — release binaries ignore this env var. Identical gate pattern to `JR_CONFIG_DIR`, applied in `src/cache.rs::cache_root()`. Pinned by `tests/config_dir_release_gate.rs`.
- `JR_SERVICE_NAME` env var overrides the keychain service name in debug builds (test isolation seam — lets keyring integration tests scope their own namespace without touching a developer's real `jr-jira-cli` entries). Debug builds only — release binaries ignore this env var and always use the compiled-in default `"jr-jira-cli"`. Gated via `#[cfg(debug_assertions)]` at `src/api/auth.rs::service_name()` (SEC-JR-SERVICE-NAME-GATE). Pinned by `tests/jr_service_name_release_gate.rs`. In `tests/oauth_refresh_integration.rs` (keyring-gated, `#[ignore]`) `JR_SERVICE_NAME` is set in-process via `set_env` serialized by the `env_lock` mutex, rather than a subprocess `.env()` call — safe because those tests are gated and sequential.
- **Release-ops repo-variable gates** — `SIGNING_ENABLED`, `HOMEBREW_TAP_REPO`, `RELEASE_GAP_FILL_ENABLED`, `SYNC_UPSTREAM_REPO`, `ATTESTATIONS_ENABLED` (all GitHub Actions repository variables, never read by `src/` code) gate the opt-in signing/backfill/gap-fill/fork-sync/attestation workflows. All unset in the canonical repo → those workflows are no-ops; downstream forks opt in. `ATTESTATIONS_ENABLED == 'true'` enables the `attest` job in `release.yml` (SLSA build provenance for release archives). Same fail-safe pattern as `JR_E2E_ENABLED`. See `docs/specs/fork-friendly-release-ops.md`.
- `JR_STDIN_IS_TTY` env var forces TTY mode in debug builds (interactive-branch test seam;
when set to `"1"`, suppresses the `src/main.rs` auto-`--no-input` flip that fires on
non-TTY stdin; release builds ignore this env var). Gated via `#[cfg(debug_assertions)]`
at `src/main.rs` (seam scope). Pinned by `tests/jr_stdin_is_tty_release_gate.rs`.
Used by interactive confirmation tests in `tests/comment_delete.rs` (VP-577-013,
VP-577-030) and `tests/comment_edit.rs` (VP-577-029 — S-577-5 interactive `--public`
confirmation gate).
- **Release-ops repo-variable gates** — `SIGNING_ENABLED`, `HOMEBREW_TAP_REPO`, `RELEASE_GAP_FILL_ENABLED`, `SYNC_UPSTREAM_REPO` (all GitHub Actions repository variables, never read by `src/` code) gate the opt-in signing/backfill/gap-fill/fork-sync workflows. All unset in the canonical repo → those workflows are no-ops; downstream forks opt in. Same fail-safe pattern as `JR_E2E_ENABLED`. See `docs/specs/fork-friendly-release-ops.md`.
- **When adding a new `JR_*` test-seam env var:** grep `CLAUDE.md` for existing `JR_*` entries and add a parallel line in the SAME commit as the code change. This is the codified doc-fallout pattern from #335/#357; first applied retroactively when `JR_BULK_UNKNOWN_GRACE_SECS` and `JR_BULK_AWAIT_TIMEOUT_SECS` shipped without documentation.
- **Citation discipline for external-tracker IDs in user-facing strings:** before citing a JRACLOUD-*/GitHub/community ID in anything a user sees (stderr, errors, JSON, hints) or in literal rustdoc, Perplexity-validate the source actually documents the symptom — issue #361 had three misattributed JRACLOUD tickets survive multiple PRs. Also ensure the string is valid in the user's env (e.g. JQL allows one ORDER BY) and keep paraphrasing rustdoc in lockstep.
- **Citation form in spec/CLAUDE.md:** prefer symbol-form (`<file>::<fn>` or `… § "<comment>"`) over line numbers, which drift on refactor. Fall back to `<file>:~NN` (`~` = approximate); never a bare `<file>:NN-MM` for new citations. (#408)
Expand Down
1 change: 1 addition & 0 deletions docs/specs/fork-friendly-release-ops.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ in both repos, so syncs are conflict-free.
| `HOMEBREW_TAP_REPO` | `owner/homebrew-name` tap repo to publish formulas to; also enables the homebrew jobs | unset |
| `RELEASE_GAP_FILL_ENABLED` | `'true'` enables the daily gap-fill schedule | unset |
| `SYNC_UPSTREAM_REPO` | `owner/repo` to merge from on a schedule (forks only) | unset |
| `ATTESTATIONS_ENABLED` | `'true'` enables the `attest` job in `release.yml` (GitHub Artifact Attestation / SLSA build provenance for the release archives; publishes the workflow's commit SHA to the public Rekor transparency log). Requires `id-token: write`; unavailable on GHES and on private forks without GitHub Enterprise Cloud | unset |
| `GITLEAKS_DISABLED` | `'true'` disables the gitleaks secret-scan job in `ci.yml`; for forks that cannot obtain a gitleaks org/commercial license or prefer an alternative scanner | unset |

This is the same fail-safe pattern as `vars.JR_E2E_ENABLED`
Expand Down