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
24 changes: 9 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
# CI — lint, build, and test Lattice on every push.
#
# Runs on the self-hosted ARC pool `homeserver-pool` (ephemeral Linux pods,
# minRunners: 0 — a fresh container is spun up per job and torn down after).
# Runs on GitHub-hosted `ubuntu-latest` inside the ghcr.io/twowells/rust-ci
# image, which bakes in rustup + the pinned toolchain + cc/make/git/pkg-config,
# so the job needs nothing provisioned on the runner itself.
#
# There is intentionally NO `pull_request` trigger. Fork PRs fire only
# pull_request events (their push happened in the fork), so with
# push/workflow_dispatch-only triggers a fork PR can never schedule a job onto
# the self-hosted runners. If you later want CI for outside contributors' PRs,
# add a SEPARATE workflow with `on: pull_request` pinned to
# `runs-on: ubuntu-latest` (GitHub-hosted, free on public repos, fork-safe).
#
# Runner provisioning required on `homeserver-pool` (bake into the ARC image or
# install via ansible):
# - rustup (the pinned 1.95 toolchain + rustfmt/clippy install
# themselves from rust-toolchain.toml on first cargo call)
# - cc/gcc, git, make, pkg-config (make check shells out to all of these)
# pull_request events (their push happened in the fork), so push/
# workflow_dispatch-only triggers mean a fork PR never schedules a job here.
# If you later want CI for outside contributors' PRs, add a SEPARATE workflow
# with `on: pull_request` (also `ubuntu-latest`, free on public repos).
name: CI

on:
Expand All @@ -28,8 +22,8 @@ concurrency:

jobs:
check:
runs-on: homeserver-pool
# The ARC pod is a bare runner with no Rust. This image bakes in rustup + the
runs-on: ubuntu-latest
# The hosted runner has no pinned Rust. This image bakes in rustup + the
# pinned toolchain + cc/make/git, so the job doesn't reprovision every run.
container: ghcr.io/twowells/rust-ci:latest
# Inside a container job GitHub's default shell is sh (dash); force bash so
Expand Down
56 changes: 36 additions & 20 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
# Release — on a vX.Y.Z tag push:
# 1. verify the tag matches Cargo.toml's version
# 2. build cross-platform binaries on the self-hosted fleet
# 2. build cross-platform binaries on GitHub-hosted runners
# 3. publish-crate to crates.io via Trusted Publishing (OIDC — no stored token)
# 4. release cut a GitHub Release with the binaries attached
# 4. release cut a GitHub Release with the binaries attached + attested
#
# Triggered ONLY by tag pushes, which require write access — a fork PR can never
# trigger this, so it is safe to run on the persistent Windows/macOS runners
# (the publish credential is never exposed to untrusted code).
# trigger this, so the publish credential is never exposed to untrusted code.
#
# Runners are GitHub-hosted: Linux builds inside ghcr.io/twowells/rust-ci (which
# carries rustup + the pinned toolchain + cc), Windows on windows-latest (MSVC),
# macOS on macos-14 (Apple Silicon, Xcode CLT). rustup self-installs the pinned
# toolchain from rust-toolchain.toml on each, so nothing is pre-provisioned.
#
# Release archives are attested with build provenance (actions/attest-build-
# provenance), giving each binary a verifiable link back to this workflow run.
#
# This is the first workflow to create GitHub Releases; v0.1.0 was tagged before
# it existed (a pre-history crates.io name-grab), so v0.2.0 is the first Release.
#
# One-time setup before the first successful run:
# - crates.io → crate Settings → Trusted Publishing → add repo TwoWells/Lattice
# and workflow file `release.yml`.
# - macOS → register the M2 Max runner (labels: self-hosted,macos,arm64).
# - Runner provisioning: rustup everywhere; MSVC Build Tools on Windows;
# Xcode Command Line Tools on macOS; cc/gcc on Linux.
name: Release

on:
Expand All @@ -25,12 +29,13 @@ on:

permissions:
contents: write # create the GitHub Release
id-token: write # OIDC token for crates.io Trusted Publishing
id-token: write # OIDC token for crates.io Trusted Publishing + provenance signing
attestations: write # write build-provenance attestations for the release archives

jobs:
# Guard: a pushed tag whose version disagrees with Cargo.toml is a mistake.
verify:
runs-on: homeserver-pool
runs-on: ubuntu-latest
outputs:
version: ${{ steps.v.outputs.version }}
steps:
Expand All @@ -56,19 +61,19 @@ jobs:
matrix:
include:
- target: x86_64-unknown-linux-gnu
runner: homeserver-pool
runner: ubuntu-latest
container: ghcr.io/twowells/rust-ci:latest
archive: tar
- target: x86_64-pc-windows-msvc
runner: [self-hosted, windows, x64]
runner: windows-latest
archive: zip
- target: aarch64-apple-darwin
runner: [self-hosted, macos, arm64]
runner: macos-14
archive: tar
runs-on: ${{ matrix.runner }}
# Linux builds inside the rust-ci image (bare ARC pod). Windows/macOS define
# no matrix container, so this evaluates empty and they build natively on the
# VM / M2 Max, which carry their own MSVC / Xcode toolchains.
# Linux builds inside the rust-ci image on the hosted runner. Windows/macOS
# define no matrix container, so this evaluates empty and they build natively
# on windows-latest / macos-14, which carry their own MSVC / Xcode toolchains.
container: ${{ matrix.container }}
steps:
- uses: actions/checkout@v6
Expand All @@ -91,8 +96,9 @@ jobs:

- name: Package (zip)
if: matrix.archive == 'zip'
# The self-hosted Windows VM has Windows PowerShell 5.1 (powershell), not
# PowerShell 7 (pwsh). Compress-Archive exists in 5.1, so this is unchanged.
# windows-latest ships both Windows PowerShell 5.1 (powershell) and
# PowerShell 7 (pwsh); we pin `powershell` (5.1) deliberately for a stable,
# always-present shell. Compress-Archive is built in there, so this works.
shell: powershell
run: |
New-Item -ItemType Directory -Force dist | Out-Null
Expand All @@ -109,8 +115,8 @@ jobs:
# a re-run (or a re-pushed tag) is a harmless no-op rather than a hard failure.
publish-crate:
needs: verify
runs-on: homeserver-pool
# Bare ARC pod → run in the toolchain image (cargo publish + the curl guard).
runs-on: ubuntu-latest
# Hosted runner → run in the toolchain image (cargo publish + the curl guard).
container: ghcr.io/twowells/rust-ci:latest
# Container default shell is sh (dash); force bash for the guard/publish steps.
defaults:
Expand Down Expand Up @@ -157,13 +163,23 @@ jobs:

release:
needs: [verify, build, publish-crate]
runs-on: homeserver-pool
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8
with:
path: dist
merge-multiple: true

# Sign a build-provenance attestation for every release archive, linking
# each binary back to this workflow run. The globs cover the Linux/macOS
# tar.gz and the Windows zip; Lattice ships no .sha256 sidecars, so these
# match exactly the archives and nothing else.
- uses: actions/attest-build-provenance@v4
with:
subject-path: |
dist/lattice-*.tar.gz
dist/lattice-*.zip

- uses: softprops/action-gh-release@v3
with:
tag_name: ${{ github.ref_name }}
Expand Down
5 changes: 4 additions & 1 deletion src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8074,7 +8074,10 @@ mod tests {
/// cross-process contention (e.g. a concurrent full-suite run saturating
/// every core) cannot inflate it — unlike a wall-clock bound. The parse is
/// single-threaded, so the calling thread's CPU time captures all the work.
const SLOW_BOUND: std::time::Duration = std::time::Duration::from_secs(10);
/// Set generously to tolerate slower CI hardware (GitHub-hosted runners are
/// markedly slower per core than the self-hosted box this was tuned on);
/// genuine quadratic blowup is orders of magnitude worse and still trips it.
const SLOW_BOUND: std::time::Duration = std::time::Duration::from_secs(30);

#[test]
fn deeply_nested_block_quotes_hit_limit() {
Expand Down
9 changes: 6 additions & 3 deletions src/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1884,9 +1884,12 @@ mod tests {
/// descheduled, so cross-process contention (e.g. a concurrent full-suite
/// run saturating every core) cannot inflate it — unlike a wall-clock
/// bound. A linear parse burns ~constant CPU regardless of load; a
/// quadratic regression burns orders of magnitude more, so this bound
/// stays meaningful without flaking.
const INLINE_SLOW_BOUND: std::time::Duration = std::time::Duration::from_secs(5);
/// quadratic regression burns orders of magnitude more (seconds → minutes).
/// The bound is set generously so slower CI hardware still clears it
/// (GitHub-hosted runners are markedly slower per core than the self-hosted
/// box this was originally tuned on — the dollar-run case measured ~5.8s
/// there), while genuine quadratic blowup never could.
const INLINE_SLOW_BOUND: std::time::Duration = std::time::Duration::from_secs(20);

#[test]
fn unclosed_bracket_run_is_not_quadratic() {
Expand Down