feat(engine): add select: first group primitive #5304
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| permissions: | |
| contents: read | |
| on: | |
| push: | |
| branches: [ "main" ] | |
| pull_request: | |
| paths-ignore: | |
| - '.beans.yml' | |
| - '.release-plz.toml' | |
| - 'cliff.toml' | |
| - 'LICENSE' | |
| - '.gitignore' | |
| - '.gitattributes' | |
| - '.gitmodules' | |
| - '.jj/**' | |
| - '.claude/**' | |
| - '.codex/**' | |
| - '.oracle-cache/**' | |
| jobs: | |
| changes: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| rust: ${{ steps.filter.outputs.rust }} | |
| docs: ${{ steps.filter.outputs.docs }} | |
| scripts: ${{ steps.filter.outputs.scripts }} | |
| styles: ${{ steps.filter.outputs.styles }} | |
| beans: ${{ steps.filter.outputs.beans }} | |
| infra: ${{ steps.filter.outputs.infra }} | |
| coverage_audits: ${{ steps.filter.outputs.coverage_audits }} | |
| semver: ${{ steps.filter.outputs.semver }} | |
| release: ${{ steps.filter.outputs.release }} | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Detect changed paths | |
| id: filter | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| BASE_REF: ${{ github.base_ref }} | |
| run: | | |
| if [ "$EVENT_NAME" = "pull_request" ]; then | |
| CHANGED=$(git diff --name-only "origin/$BASE_REF...HEAD") | |
| else | |
| CHANGED=$(git diff --name-only HEAD^ HEAD 2>/dev/null || git diff --name-only HEAD) | |
| fi | |
| has() { echo "$CHANGED" | grep -qE "$1" && echo "true" || echo "false"; } | |
| { | |
| echo "rust=$(has '^(crates/|Cargo\.toml$|Cargo\.lock$|rust-toolchain\.toml$)')" | |
| echo "docs=$(has '\.md$|^(docs/|CONTRIBUTING\.md$|examples/|\.github/)')" | |
| echo "scripts=$(has '^(scripts/|examples/)')" | |
| echo "styles=$(has '^(styles/|locales/|docs/schemas/)')" | |
| echo "beans=$(has '^(registry/|locales/|templates/|\.beans\.yml$|\.beans/)')" | |
| echo "infra=$(has '^(scripts/|crates/)')" | |
| # The freshness check builds the source Citum binary, so any Rust crate can change rendered audit output. | |
| # Keep docs-only and metadata-only changes lightweight while covering every registered audit input. | |
| echo "coverage_audits=$(has '^(Cargo\.(toml|lock)$|rust-toolchain\.toml$|crates/|styles/|locales/|docs/schemas/|tests/fixtures/|docs/architecture/audits/|styles-legacy$|scripts/package\.json$|scripts/(check-style-coverage-audits(\.test)?\.js|style-coverage-review(\.test)?\.js|lib/(report-metadata|style-coverage-audits)\.js|report-data/(report-provenance\.yaml|style-coverage-(audit-manifest|packet)\.schema\.json)))')" | |
| echo "semver=$(has '^crates/(csl-legacy|citum-schema-data|citum-schema-style|citum-schema|citum-migrate|citum-engine|citum-cli|citum-bindings)/')" | |
| echo "release=$(has '^(\.github/workflows/(ci|release)\.yml$|Cargo\.toml$|Cargo\.lock$|RELEASING\.md$|scripts/(build-jsr-package|install|publish-crates|release-binary)\.sh$|crates/)')" | |
| } >> "$GITHUB_OUTPUT" | |
| hygiene-docs: | |
| name: Hygiene Checks — Docs | |
| needs: changes | |
| if: ${{ needs.changes.outputs.docs == 'true' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Run repository hygiene (alint) | |
| uses: asamarts/alint@9a341559646a0a128957a0f70671e0835fb38dcb # v0.13.0 | |
| hygiene-beans: | |
| name: Hygiene Checks — Beans | |
| needs: changes | |
| if: ${{ needs.changes.outputs.beans == 'true' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Install beans CLI | |
| run: | | |
| curl -fsSL \ | |
| https://github.com/hmans/beans/releases/download/v0.4.0/beans_Linux_x86_64.tar.gz \ | |
| -o /tmp/beans.tar.gz | |
| tar -xzf /tmp/beans.tar.gz -C /tmp | |
| sudo install -m 0755 /tmp/beans /usr/local/bin/beans | |
| - name: Set up Node | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 | |
| with: | |
| node-version: "22" | |
| - name: Install script dependencies | |
| run: npm install --prefix scripts | |
| - name: Run bean hygiene regression tests | |
| run: ./scripts/test-beans-hygiene.sh | |
| - name: Check bean hygiene | |
| run: ./scripts/check-bean-hygiene.sh | |
| hygiene-scripts: | |
| name: Hygiene Checks — Scripts | |
| needs: changes | |
| if: ${{ needs.changes.outputs.scripts == 'true' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Node | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 | |
| with: | |
| node-version: "22" | |
| - name: Install script dependencies | |
| run: npm install --prefix scripts | |
| - name: Run script regression tests | |
| run: node --test scripts/*.test.js scripts/lib/*.test.js | |
| - name: Run Python script regression tests | |
| run: | | |
| python3 -m unittest \ | |
| scripts/test_infer_release_bump.py \ | |
| scripts/test_coverage_analysis.py \ | |
| scripts/test_release_workflow.py | |
| - name: Run hook regression tests | |
| run: ./scripts/test_pre_push_policy_base.sh | |
| release-dry-runs: | |
| name: Release Dry Runs | |
| needs: changes | |
| if: ${{ needs.changes.outputs.release == 'true' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| with: | |
| persist-credentials: false | |
| - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| with: | |
| targets: wasm32-unknown-unknown | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 | |
| with: | |
| node-version: "22" | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| key: release-dry-runs | |
| - name: Install wasm-pack | |
| uses: taiki-e/install-action@9bcaee1dcae34154180f412e2fa69355a7cda9f6 # v2 | |
| with: | |
| tool: wasm-pack | |
| - name: Dry-run crates.io publish scripts | |
| run: | | |
| ./scripts/publish-crates.sh --dry-run | |
| ./scripts/publish-crates.sh --dry-run --skip citum | |
| ./scripts/publish-crates.sh --dry-run --only citum | |
| - name: Build JSR package | |
| run: ./scripts/build-jsr-package.sh | |
| - name: Dry-run JSR publish | |
| working-directory: target/jsr/citum | |
| run: npx --yes jsr publish --dry-run | |
| hygiene-infra: | |
| name: Hygiene Checks — Infra | |
| needs: changes | |
| if: ${{ needs.changes.outputs.infra == 'true' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Node | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 | |
| with: | |
| node-version: "22" | |
| - name: Install script dependencies | |
| run: npm install --prefix scripts | |
| - name: Validate production styles and structure | |
| run: ./scripts/validate-production-styles.sh | |
| - name: Check testing infrastructure contracts | |
| run: node scripts/check-testing-infra.js | |
| - name: Check facade crate boundaries | |
| run: ./scripts/check-facade-crate.sh | |
| coverage-audit-freshness: | |
| name: Coverage Audit Freshness | |
| needs: changes | |
| if: ${{ needs.changes.outputs.coverage_audits == 'true' }} | |
| runs-on: ubuntu-latest | |
| env: | |
| CARGO_TERM_COLOR: always | |
| CARGO_INCREMENTAL: "0" | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Initialize registered authority source | |
| run: git submodule update --init --depth 1 styles-legacy | |
| - name: Set up Rust | |
| uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable | |
| - name: Set up Node | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 | |
| with: | |
| node-version: "22" | |
| - name: Cache Rust dependencies | |
| uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| shared-key: "citum-core-coverage-audits" | |
| cache-workspace-crates: true | |
| - name: Install script dependencies | |
| run: npm install --prefix scripts | |
| - name: Validate registered coverage audits | |
| run: node scripts/check-style-coverage-audits.js | |
| benchmark-wasm: | |
| name: WASM Benchmark Smoke Test | |
| needs: changes | |
| if: ${{ needs.changes.outputs.infra == 'true' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| with: | |
| persist-credentials: false | |
| - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| with: | |
| targets: wasm32-unknown-unknown | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| key: benchmark-wasm | |
| - name: Install wasm-pack | |
| uses: taiki-e/install-action@9bcaee1dcae34154180f412e2fa69355a7cda9f6 # v2 | |
| with: | |
| tool: wasm-pack | |
| - name: Set up Node | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 | |
| with: | |
| node-version: "22" | |
| - name: Build WASM bindings | |
| working-directory: crates/citum-bindings | |
| run: wasm-pack build --target nodejs --features full-wasm | |
| - name: Run workflow benchmark (CI mode) | |
| run: node scripts/benchmark-wasm-workflow.js --ci | |
| rust-ci: | |
| name: Rust CI (Lint + Test) | |
| needs: changes | |
| if: ${{ needs.changes.outputs.rust == 'true' }} | |
| runs-on: ubuntu-latest | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUSTFLAGS: "-C link-arg=-fuse-ld=mold" | |
| CARGO_INCREMENTAL: "0" | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| with: | |
| fetch-depth: 1 | |
| persist-credentials: false | |
| - name: Set up Rust | |
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| with: | |
| components: rustfmt, clippy | |
| - name: Install mold linker | |
| run: sudo apt-get update && sudo apt-get install -y mold | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@9bcaee1dcae34154180f412e2fa69355a7cda9f6 # v2 | |
| with: | |
| tool: cargo-nextest | |
| - name: Cache Rust dependencies | |
| uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| shared-key: "citum-core-rust" | |
| cache-workspace-crates: true | |
| # Restored target caches can contain v8 build metadata without the | |
| # downloaded native archive; rebuild only this package's artifacts. | |
| - name: Invalidate cached rusty_v8 build artifacts | |
| run: cargo clean -p v8 | |
| - name: Check formatting | |
| run: cargo fmt --all -- --check | |
| - name: Lint with Clippy | |
| run: cargo clippy --workspace --all-targets --all-features -- -D warnings | |
| - name: Enforce public API docs | |
| run: cargo doc --no-deps --workspace --all-features | |
| - name: Run tests | |
| run: cargo nextest run --all-features | |
| - name: Verify JSON schemas are up to date | |
| run: | | |
| tmp_dir="$(mktemp -d)" | |
| cargo run --bin citum --all-features -- schema --out-dir "$tmp_dir" | |
| diff -ru --exclude="*.html" "$tmp_dir" docs/schemas | |
| hygiene-data-model-docs: | |
| name: Hygiene Checks — Data Model Docs | |
| needs: changes | |
| if: ${{ needs.changes.outputs.docs == 'true' || needs.changes.outputs.scripts == 'true' || needs.changes.outputs.styles == 'true' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Node | |
| uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 | |
| with: | |
| node-version: "22" | |
| - name: Install Node dependencies | |
| run: npm install --prefix scripts | |
| - name: Verify data model reference docs are up to date | |
| run: | | |
| tmp_dir="$(mktemp -d)" | |
| node scripts/build-data-model-reference.js --out-dir "$tmp_dir" | |
| diff -ru "$tmp_dir/generated" docs/reference/generated | |
| diff -u "$tmp_dir/BIBLATEX_MAPPING.md" docs/reference/BIBLATEX_MAPPING.md | |
| diff -u "$tmp_dir/NATIVE_FORMAT.md" docs/reference/NATIVE_FORMAT.md | |
| - name: Validate styles and locales against published schemas | |
| run: node scripts/validate-schemas.js --scope=styles,locales | |
| - name: Reject empty objects in tracked styles | |
| run: node scripts/style-structure-lint.js --rule STYLE012 | |
| - name: Check internal doc links | |
| run: | | |
| node scripts/build-doc-pages.js | |
| node scripts/build-author-guide.js | |
| node scripts/check-doc-links.js | |
| security: | |
| name: Security Audit | |
| needs: changes | |
| if: ${{ needs.changes.outputs.rust == 'true' }} | |
| runs-on: ubuntu-latest | |
| env: | |
| CARGO_TERM_COLOR: always | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| with: | |
| fetch-depth: 1 | |
| persist-credentials: false | |
| - name: Set up Rust | |
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| - name: Set up nightly Rust for fuzzing | |
| uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly | |
| - name: Install security tools | |
| uses: taiki-e/install-action@9bcaee1dcae34154180f412e2fa69355a7cda9f6 # v2 | |
| with: | |
| tool: cargo-audit,cargo-deny,cargo-fuzz | |
| - name: Check RustSec advisories | |
| # cargo-deny below enforces yanks and supports exact package exceptions. | |
| run: cargo audit --deny warnings --no-yanked | |
| - name: Check dependency policy | |
| run: cargo deny check advisories licenses bans sources | |
| - name: Build fuzz targets | |
| run: cargo +nightly fuzz build --target x86_64-unknown-linux-gnu | |
| semver-check: | |
| name: API Semver Check | |
| needs: changes | |
| if: >- | |
| ${{ github.event_name == 'pull_request' && | |
| needs.changes.outputs.semver == 'true' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Set up Rust | |
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| - name: Cache Rust dependencies | |
| uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| shared-key: "citum-core-semver" | |
| cache-workspace-crates: true | |
| - name: Install cargo-semver-checks | |
| uses: taiki-e/install-action@9bcaee1dcae34154180f412e2fa69355a7cda9f6 # v2 | |
| with: | |
| tool: cargo-semver-checks | |
| - name: Check publishable crates for semver violations | |
| continue-on-error: true | |
| run: | | |
| cargo semver-checks \ | |
| --workspace |