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
53 changes: 29 additions & 24 deletions .github/workflows/commitlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
# SPDX-License-Identifier: MPL-2.0

# ─────────────────────────────────────────────────────────────────────────────
# Commitlint — Conventional Commits advisory check on PR titles
# Commitlint — Conventional Commits REQUIRED check on PR titles
#
# Phase R1a of `docs/architecture/release-automation-plan.md`.
# Phase R1b of `docs/architecture/release-automation-plan.md`
# (promoted from the R1a advisory gate on 2026-06-10 after ≥1 month of
# observation; the trailing 80 first-parent merges on `main` were 100%
# conformant at promotion time).
#
# Why this exists:
#
Expand All @@ -19,21 +22,22 @@
# This workflow surfaces non-conforming titles at PR-open time so the
# author can fix them before merge.
#
# Why advisory (not enforcing):
# Why required (now enforcing):
#
# Phase R1a is the OBSERVATION period. We're at ~100% adherence over
# the last 3 days (24/24 PRs) but only 83.3% over the last 30 days
# (75/90 PRs). The 15 non-conforming PRs use project-internal prefixes
# like `security:`, `bench:`, `shmem:`, `stream-stress:`,
# `cross-tool-benchmark:`, `gitignore:` (see
# `docs/architecture/release-automation-baseline.md` §4). Some of those
# may be re-tagged as `chore(security):`, `bench:` (if we choose to
# accept it as a real type), etc. Phase R1a gathers the data; Phase
# R1b (≥1 month later) makes the gate mandatory using the refined type
# list informed by the observation.
# Phase R1a (2026-04-25 → 2026-06-10) was the OBSERVATION period. At
# R1a start adherence was ~100% over the last 3 days (24/24 PRs) but
# only 83.3% over the last 30 days (75/90 PRs); the non-conforming PRs
# used project-internal prefixes like `security:`, `bench:`, `shmem:`.
# Over the observation window the project converged on the 11 standard
# Conventional Commits types (the `security:` carve-out was migrated to
# `fix(security):` / `chore(security):` — see the R1b CC-type
# convergence deviation row). At promotion the trailing 80 first-parent
# merges on `main` were 100% conformant, so the gate is now mandatory
# with negligible false-positive risk.
#
# In advisory mode this workflow ALWAYS exits 0. Non-conforming titles
# produce a sticky PR comment, never a merge block.
# In required mode this workflow exits non-zero on a non-conforming
# title AND posts the sticky PR comment, so the author both sees the
# guidance and is blocked from merging until the title conforms.
#
# Sticky-comment design:
#
Expand All @@ -47,7 +51,6 @@
#
# What this does NOT do:
#
# • Block merge. Always exits 0. R1b will flip a single line to enforce.
# • Validate individual commit messages on the feature branch. UFFS
# uses squash-merge exclusively, so the PR title (which becomes the
# squash subject) is the only commit message that lands on `main`.
Expand All @@ -62,7 +65,7 @@
# (the spec that defines the type list and breaking-change syntax)
# • plan §2.8 (current adherence baseline) and §3 (R1a → R1b transition)

name: "📝 Commitlint (advisory)"
name: "📝 Commitlint (required)"

on:
pull_request:
Expand Down Expand Up @@ -132,7 +135,7 @@ jobs:
echo "| --- | --- |"
echo "| Title | \`${TITLE}\` |"
echo "| Pattern | \`${PATTERN}\` |"
echo "| Mode | **advisory** (Phase R1a) — never blocks merge |"
echo "| Mode | **required** (Phase R1b) — blocks merge until the title conforms |"
echo ""
} >> "$GITHUB_STEP_SUMMARY"

Expand Down Expand Up @@ -200,9 +203,9 @@ jobs:

---

**🟡 This check is ADVISORY during release-automation Phase R1a** — non-conforming titles do **not** block merge. Phase R1b (scheduled after ≥1 month of observation) will make this a required gate. Until then, please update the title when convenient; this comment will auto-delete on the next workflow run once the title conforms.
**🔴 This check is REQUIRED (release-automation Phase R1b)** — a non-conforming title **blocks merge**. Edit the PR title to match the pattern above; this check re-runs on every title edit and turns green (and this comment auto-deletes) once the title conforms.

*(Workflow: \`.github/workflows/commitlint.yml\` · Plan: [\`docs/architecture/release-automation-plan.md\`](https://github.com/${GITHUB_REPOSITORY}/blob/main/docs/architecture/release-automation-plan.md) Phase R1a)*
*(Workflow: \`.github/workflows/commitlint.yml\` · Plan: [\`docs/architecture/release-automation-plan.md\`](https://github.com/${GITHUB_REPOSITORY}/blob/main/docs/architecture/release-automation-plan.md) Phase R1b)*
EOF
)

Expand All @@ -229,7 +232,9 @@ jobs:
--body "${BODY}"
fi

# ADVISORY MODE: exit 0 even on non-conformance.
# Phase R1b will replace this single line with `exit 1` to
# promote the check from advisory to required.
exit 0
# REQUIRED MODE (Phase R1b): exit non-zero on non-conformance
# so the check fails and (once added to branch protection's
# required-status-checks for `main`) blocks merge until the
# author fixes the title. The sticky comment above tells them
# how. Promoted from the R1a advisory `exit 0` on 2026-06-10.
exit 1
51 changes: 41 additions & 10 deletions .github/workflows/release-plz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -428,13 +428,16 @@ jobs:
# dormancy switch, NOT a lint-suppression hack — the job still never
# runs until the maintainer deliberately sets the variable.
#
# Enabling this in R8 requires:
# The OIDC auth + publish steps below are now wired (R8 bootstrap
# complete: uffs-time + uffs-text published manually with a token on
# 2026-06-10). Activating this automated path requires only:
# 1. Configure crates.io crate-level trusted publishers (web UI)
# — Workflow filename `release-plz.yml`, Environment
# `crates.io-publish` (must match `environment:` below exactly)
# 2. Create the `crates.io-publish` environment with required
# reviewers (manual approval gate for the dress rehearsal)
# reviewers (manual approval gate for each published version)
# 3. Set repo variable `ENABLE_CRATES_IO_PUBLISH = true`
# (Settings → Secrets and variables → Actions → Variables)
# 4. Uncomment the publish step below
#
# OIDC replaces the legacy `CARGO_REGISTRY_TOKEN` secret entirely —
# no long-lived token is ever stored once trusted publishing is on.
Expand Down Expand Up @@ -476,10 +479,38 @@ jobs:
echo "OIDC token endpoint: $ACTIONS_ID_TOKEN_REQUEST_URL"
echo "OIDC request token available: ${{ secrets.ACTIONS_ID_TOKEN_REQUEST_TOKEN != '' }}"

# Placeholder for R8 — actual publish steps will go here
# - name: Publish to crates.io (R8)
# env:
# # No CARGO_REGISTRY_TOKEN needed — OIDC handles auth
# run: |
# cargo publish -p uffs-time --dry-run # Dry-run first
# # Actual publish gated by manual approval in R8
# Mint a short-lived crates.io token via OIDC trusted publishing.
# No long-lived CARGO_REGISTRY_TOKEN secret is ever stored — the
# token is exchanged at runtime against the trusted-publisher
# registrations on crates.io (one per publishable crate, bound to
# this workflow filename + the `crates.io-publish` environment).
- name: Authenticate with crates.io (OIDC)
id: auth
uses: rust-lang/crates-io-auth-action@bbd81622f20ce9e2dd9622e3218b975523e45bbe # v1.0.4

# Publish the publishable crates, dependency-ordered. The set is
# derived from `cargo metadata` (publish != []) so it adapts as
# more crates flip to publishable — today this is exactly
# uffs-time + uffs-text (both dependency-free leaves, any order).
#
# `cargo publish` is a no-op-error if the version already exists,
# so in steady state release-plz bumps the version first; this job
# then publishes the freshly bumped version. `--locked` enforces
# the committed Cargo.lock. `--no-verify` is intentionally NOT
# used — we want the pre-upload build to catch packaging errors.
- name: Publish to crates.io (OIDC, dependency-ordered)
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: |
set -euo pipefail
mapfile -t crates < <(
cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[] | select(.publish != []) | .name' \
| sort
)
echo "Publishable crates: ${crates[*]}"
for crate in "${crates[@]}"; do
echo "::group::cargo publish -p ${crate}"
cargo publish -p "${crate}" --locked
echo "::endgroup::"
done
Loading
Loading