diff --git a/.github/workflows/no-vanity-metrics.yml b/.github/workflows/no-vanity-metrics.yml index 7735b4571..1c819bd51 100644 --- a/.github/workflows/no-vanity-metrics.yml +++ b/.github/workflows/no-vanity-metrics.yml @@ -6,20 +6,67 @@ name: no-vanity-metrics # permanent sync debt with the websites; the open-standards posture is # "describe the shape, not the size." See CONTRIBUTING.md for the # rationale and the opt-out marker. +# +# Two more jobs hold WAI's public text (wai/) to its writing rules +# (wai/CONTRIBUTING.md, *Public text*): no comparative or promotional phrasing +# (a public phrase list), and no company, vendor, customer or product names +# (patterns kept private, in the WAI_NAME_DENYLIST secret; prints locations +# only). Both checks fail closed: a grep error or a pattern that does not compile +# fails the job, and scripts/test-public-text-checks.sh proves it on every run. +# The secret is given to one step, on a push to main only: a pull request runs +# code from its own branch, and a step of it that held the list could print it to +# a public log. Without the secret, a push to main fails; a pull request shows a +# failed (continue-on-error) step saying the name check did not run. on: pull_request: paths: - '**/*.md' + # The public-text checks below scan WAI's code comments and data too. + - 'wai/**/*.rs' + - 'wai/**/*.toml' + - 'wai/**/*.py' + - 'wai/**/*.sh' + - 'wai/**/*.js' + - 'wai/**/*.mjs' + - 'wai/**/*.json' + - 'wai/**/*.html' + - 'wai/**/*.css' + - 'wai/**/*.ttl' + - 'wai/**/*.h' + - 'wai/**/*.c' + - 'wai/**/*.txt' + - 'wai/**/.gitignore' - '.github/workflows/no-vanity-metrics.yml' - 'scripts/check-no-vanity-metrics.sh' + - 'scripts/check-comparative-prose.sh' + - 'scripts/check-public-names.sh' + - 'scripts/test-public-text-checks.sh' push: branches: - main paths: - '**/*.md' + # The public-text checks below scan WAI's code comments and data too. + - 'wai/**/*.rs' + - 'wai/**/*.toml' + - 'wai/**/*.py' + - 'wai/**/*.sh' + - 'wai/**/*.js' + - 'wai/**/*.mjs' + - 'wai/**/*.json' + - 'wai/**/*.html' + - 'wai/**/*.css' + - 'wai/**/*.ttl' + - 'wai/**/*.h' + - 'wai/**/*.c' + - 'wai/**/*.txt' + - 'wai/**/.gitignore' - '.github/workflows/no-vanity-metrics.yml' - 'scripts/check-no-vanity-metrics.sh' + - 'scripts/check-comparative-prose.sh' + - 'scripts/check-public-names.sh' + - 'scripts/test-public-text-checks.sh' jobs: scan: @@ -31,3 +78,43 @@ jobs: - name: Run scanner run: bash scripts/check-no-vanity-metrics.sh + + wai-comparative-prose: + name: WAI public text names no comparison + runs-on: linux + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: The two public-text checks fail closed (self-test) + run: bash scripts/test-public-text-checks.sh + - name: Run the comparative-prose check + run: bash scripts/check-comparative-prose.sh + + wai-public-names: + name: WAI public text names no company or product + runs-on: linux + steps: + - name: Checkout + uses: actions/checkout@v4 + # The list reaches this step only, and only on a push to main, whose code + # has been merged. Without the list the check cannot run, and that fails. + - name: Run the public-name check + if: github.event_name != 'pull_request' + env: + # Newline-separated patterns, never committed: a public list would + # publish the names it keeps out. + WAI_NAME_DENYLIST: ${{ secrets.WAI_NAME_DENYLIST }} + run: | + if [ -z "$WAI_NAME_DENYLIST" ]; then + echo "::error::WAI_NAME_DENYLIST is not set: the public-name check cannot run" + exit 1 + fi + bash scripts/check-public-names.sh + # A pull request never receives the list. The step fails visibly without + # failing the job; the push to main runs the check. + - name: The public-name check did not run on this pull request + if: github.event_name == 'pull_request' + continue-on-error: true + run: | + echo "::warning::the public-name check does not run on a pull request (its pattern list is never given to pull-request code); it runs on the push to main" + exit 1 diff --git a/.github/workflows/wai-byte-equality.yml b/.github/workflows/wai-byte-equality.yml index 6b7b16078..a4f938924 100644 --- a/.github/workflows/wai-byte-equality.yml +++ b/.github/workflows/wai-byte-equality.yml @@ -2,8 +2,8 @@ name: wai cross-architecture byte-equality # The `replicate` determinism contract, checked on real silicon. WAI claims its # integer-exact decode paths (rANS entropy coder, integer fixed-point synthesis) -# produce BYTE-IDENTICAL output on every machine. This matrix proves it: five -# OS/arch combinations decode the same fixtures and assert the same golden +# produce BYTE-IDENTICAL output on every machine. This matrix proves it: every +# OS/arch leg of the matrix below decodes the same fixtures and asserts the same golden # bytes — rANS streams produced by CompressAI's upstream encoder, and an # int_synth output hash produced on the reference machine (macOS arm64). One # diverging byte anywhere fails the suite. @@ -77,16 +77,18 @@ jobs: strategy: fail-fast: false matrix: - # Self-hosted fleet labels, not GitHub-hosted images. macos-x86_64 is + # Self-hosted fleet labels, not hosted images. macos-x86_64 is # dropped: no Intel-Mac runner exists in the fleet, and by the same # argument the cargo-classes matrix already makes, it adds no arch - # coverage — x86_64 is held by linux-x86_64 and windows-x86_64, arm64 - # by linux-aarch64 and the macos-arm64 reference. + # coverage — x86_64 is held by linux-x86_64, arm64 by linux-aarch64 + # and the macos-arm64 reference. + # Windows x86_64 leg removed 2026-09-30: runner offline. Restore by + # re-adding `- { os: windows, label: windows-x86_64 }` here once the + # runner is back online. include: - { os: linux, label: linux-x86_64 } - { os: linux-arm64, label: linux-aarch64 } - { os: macos, label: macos-arm64 (reference) } - - { os: windows, label: windows-x86_64 } steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/wai-conformance.yml b/.github/workflows/wai-conformance.yml index 881f06745..9446e6443 100644 --- a/.github/workflows/wai-conformance.yml +++ b/.github/workflows/wai-conformance.yml @@ -2,10 +2,10 @@ name: WAI conformance # WAI's load-bearing claim is bit-exact reconstruction on every machine, # no tolerance parameter. This workflow enforces it mechanically: every -# cargo-class corpus — and the one neural decoder that can make the same -# promise, the integer-synthesis path (i64-only, no float ONNX) — is -# re-derived on a four-platform matrix (x86-64 + arm64 across Linux, macOS, -# Windows) and must reproduce its frozen BLAKE3 hashes; and the integer +# cargo-class corpus — and the integer-exact neural decoders, which make the +# same promise (the registry's NeuralIntegerExact class: i64-only, no float +# ONNX) — is re-derived on every OS/arch leg of the matrix below (x86-64 and +# arm64) and must reproduce its frozen BLAKE3 hashes; and the integer # byte-equality goldens (rANS / int_synth / JPEG-AI mANS, generated once on # a reference machine) must decode identically everywhere. All of it is pure # Rust (no C codec chain, no ONNX Runtime), so the matrix needs no system @@ -21,6 +21,15 @@ on: - 'wai/wai-quantum/**' - 'wai/*-conformance/**' - 'wai/byte-exact-conformance/**' + # The registry export and the two library tests that read files outside + # wai-rs (the codec glossary, SPEC.md) must fire this workflow too: a + # hand edit of an exported file, of SPEC §5 or of the glossary is checked + # here. + - 'wai/registry/**' + - 'wai/vocab/**' + - 'wai/glossary/**' + - 'wai/SPEC.md' + - 'wai/REGISTRY.md' - '.github/workflows/wai-conformance.yml' pull_request: paths: @@ -30,12 +39,23 @@ on: - 'wai/wai-quantum/**' - 'wai/*-conformance/**' - 'wai/byte-exact-conformance/**' + # The registry export and the two library tests that read files outside + # wai-rs (the codec glossary, SPEC.md) must fire this workflow too: a + # hand edit of an exported file, of SPEC §5 or of the glossary is checked + # here. + - 'wai/registry/**' + - 'wai/vocab/**' + - 'wai/glossary/**' + - 'wai/SPEC.md' + - 'wai/REGISTRY.md' - '.github/workflows/wai-conformance.yml' # No global `shell:` default. The Windows runner is a host executor whose # `bash` is the WSL stub (a different OS), so shell-specific steps below pick # their shell per-OS: bash on unix, pwsh on Windows. Bare `cargo …` steps take -# each runner's native default, which runs cargo identically either way. +# each runner's native default, which runs cargo identically either way. The +# Windows legs are out of the matrices for now (see the comment on each matrix); +# their pwsh steps stay, inactive, so restoring a leg is a one-line change. jobs: cargo-classes: @@ -44,12 +64,13 @@ jobs: strategy: fail-fast: false matrix: - # Self-hosted fleet labels. Both architectures across all three OS - # families: x86_64 by linux + windows, arm64 by linux-arm64 + macos — - # what matters for the Fx integer floor is the arch, not the OS. No - # Intel-Mac runner exists in the fleet, and it would add no arch - # coverage anyway. - os: [linux, linux-arm64, macos, windows] + # Self-hosted fleet labels. Both architectures: x86_64 by linux, arm64 + # by linux-arm64 + macos — what matters for the Fx integer floor is the + # arch, not the OS. No Intel-Mac runner exists in the fleet, and it + # would add no arch coverage anyway. + # Windows x86_64 leg removed 2026-09-30: runner offline. Restore by + # re-adding `windows` here once the runner is back online. + os: [linux, linux-arm64, macos] steps: - uses: actions/checkout@v5 @@ -125,15 +146,32 @@ jobs: # tests/int_mlicpp_preflight.rs (its own binary: it installs a counting global # allocator) checks that a header the model cannot fit is refused before the # decode allocates anything sized from it. + # tests/int_audio.rs and tests/int_splat.rs decode the committed wai.audio.int_codec and + # wai.splat.int_codec vectors against the hashes the Python integer reference computed at + # export, and check each vector's metadata. Cargo.toml declares them, and the other + # feature-gated targets named here, with required-features, so if this step ever loses a + # feature, cargo stops with an error instead of compiling an empty binary that reports ok. - name: Fast integer kernels ≡ reference kernels (overflow checks on) working-directory: wai/wai-rs - run: cargo test --no-default-features --features neural_int,video_int,audio_int,world_int,splat_int --lib --test int_kernels_vectors --test int_kernels_geometry --test int_mlicpp --test int_mlicpp_preflight + run: cargo test --no-default-features --features neural_int,video_int,audio_int,world_int,splat_int --lib --test int_kernels_vectors --test int_kernels_geometry --test int_mlicpp --test int_mlicpp_preflight --test int_audio --test int_splat - name: Fast integer kernels ≡ reference kernels (release, overflow checks off) working-directory: wai/wai-rs - run: cargo test --release --no-default-features --features neural_int,video_int,audio_int,world_int,splat_int --lib --test int_kernels_vectors --test int_kernels_geometry --test int_mlicpp --test int_mlicpp_preflight - - name: Carriage conformance — every registered capability round-trips byte-exact + run: cargo test --release --no-default-features --features neural_int,video_int,audio_int,world_int,splat_int --lib --test int_kernels_vectors --test int_kernels_geometry --test int_mlicpp --test int_mlicpp_preflight --test int_audio --test int_splat + # carriage: every registered capability round-trips byte-exact. cli_wrap_intent and + # cli_registry run the `wai` binary, which builds without the codecs feature: the + # intent rule of `wai wrap`, its refusal of candidate capabilities (SPEC §5), its media + # class, and `wai registry export --check` on the tree and on a drifted copy. + - name: Carriage conformance and the wai CLI (wrap, registry export) working-directory: wai/wai-rs - run: cargo test --no-default-features --test carriage + run: cargo test --no-default-features --test carriage --test cli_wrap_intent --test cli_registry + # The machine-readable registry (wai/registry/, wai/vocab/v2.*, the table in + # wai/REGISTRY.md) is generated from wai-rs/src/codecs/registry.rs. Regenerate it in + # memory and fail on any difference; the message says how to regenerate. One leg: the + # export is the same text on every platform. + - name: The registry export is up to date (linux) + if: matrix.os == 'linux' + working-directory: wai/wai-rs + run: cargo run -q --no-default-features --bin wai -- registry export --check # The corpus-verify loop is genuinely shell-specific (a helper that groups # log output and propagates the first non-zero exit), so it ships once per @@ -200,18 +238,40 @@ jobs: v oer wai_oer_vectors ../oer-conformance v claims wai_claim_vectors ../claim-conformance + # The generated v2 vocabulary (wai/vocab/v2.*) is checked as a consumer uses it: + # sample manifests, typed and untyped, WAI1 and WAI2, are expanded through the + # JSON-LD context exactly as written on the wire and validated against the SHACL + # shapes, each with the result it must give (a candidate in any place is + # rejected; a media value no revision lists is accepted). One leg: RDF tooling, + # not arithmetic. The tool versions are pinned. + vocab-shapes: + name: v2 vocabulary · manifests validate as stated + runs-on: linux + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install the RDF tools + shell: bash + run: python -m pip install rdflib==7.6.0 PyLD==3.3.0 pyshacl==0.40.1 + - name: Sample manifests against vocab/v2.shapes.ttl + shell: bash + run: python wai/vocab/check_shapes.py + byte-exact: name: cross-arch byte-equality · ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - # Self-hosted fleet labels. Both architectures across all three OS - # families: x86_64 by linux + windows, arm64 by linux-arm64 + macos — - # what matters for the Fx integer floor is the arch, not the OS. No - # Intel-Mac runner exists in the fleet, and it would add no arch - # coverage anyway. - os: [linux, linux-arm64, macos, windows] + # Self-hosted fleet labels. Both architectures: x86_64 by linux, arm64 + # by linux-arm64 + macos — what matters for the Fx integer floor is the + # arch, not the OS. No Intel-Mac runner exists in the fleet, and it + # would add no arch coverage anyway. + # Windows x86_64 leg removed 2026-09-30: runner offline. Restore by + # re-adding `windows` here once the runner is back online. + os: [linux, linux-arm64, macos] steps: - uses: actions/checkout@v5 diff --git a/.github/workflows/wai-energy-receipts.yml b/.github/workflows/wai-energy-receipts.yml index 98eb88a15..07dbf3bf7 100644 --- a/.github/workflows/wai-energy-receipts.yml +++ b/.github/workflows/wai-energy-receipts.yml @@ -44,8 +44,8 @@ name: WAI energy receipts # - build wai-web, or the wasm32-wasip2 component of wai-quantum-serve. # - verify the conformance corpora; wai-conformance.yml does that. # - run on Windows. The pinned bytes are integers and hashes and make no -# OS-specific claim, and the conformance matrix covers Windows for the -# byte-exact decode. +# OS-specific claim; the conformance matrix has no Windows leg either (its +# runner is offline). # # It triggers only on changes under the paths listed below; a change to # wai-web alone does not trigger it. diff --git a/.github/workflows/wai-sota-vectors.yml b/.github/workflows/wai-sota-vectors.yml index cf93aa572..338e9e45a 100644 --- a/.github/workflows/wai-sota-vectors.yml +++ b/.github/workflows/wai-sota-vectors.yml @@ -107,6 +107,8 @@ on: - 'wai/tools/wai_sota_*' - 'wai/tools/wai_int_video_sota_keyframe_encode.py' - 'wai/tools/requirements-sota-int.txt' + # The wasm32 decode of the same vectors (the last steps below). + - 'wai/wai-web/**' - '.github/workflows/wai-sota-vectors.yml' pull_request: paths: @@ -127,6 +129,8 @@ on: - 'wai/tools/wai_sota_*' - 'wai/tools/wai_int_video_sota_keyframe_encode.py' - 'wai/tools/requirements-sota-int.txt' + # The wasm32 decode of the same vectors (the last steps below). + - 'wai/wai-web/**' - '.github/workflows/wai-sota-vectors.yml' # Weekly, independent of changes: the job depends on artifacts fetched from # outside the repository (the checkpoint, the pinned wheels), and their @@ -252,3 +256,25 @@ jobs: echo "::error::a vector test skipped instead of checking its input" exit 1 fi + # The same vectors decoded by the wasm32 build of wai-web (the browser + # sink), in the same job so they share $WAI_SOTA_INT_DIR. The verify script + # fails on a missing artifact here (WAI_REQUIRE_SOTA_VECTORS=1, and the + # directory is named), and compares the wasm decode's RGB hash, for the + # JSON and the compact binary model, with the native export's. + - name: wasm32 toolchain (target, wasm-bindgen CLI pinned to wai-web's lock) + shell: bash + run: | + rustup target add wasm32-unknown-unknown + cargo install --locked wasm-bindgen-cli --version 0.2.127 + - uses: actions/setup-node@v4 + with: + node-version: '22' + - name: wasm32 decode of the int_hyper vectors equals the native reference + shell: bash + working-directory: wai/wai-web + run: | + set -euo pipefail + cargo build --locked --release --target wasm32-unknown-unknown + wasm-bindgen --target nodejs --out-dir demo/pkg-node --out-name wai_web \ + target/wasm32-unknown-unknown/release/wai_web.wasm + node demo/verify_int_hyper_wasm.mjs ./pkg-node/wai_web.js "$WAI_SOTA_INT_DIR" diff --git a/.github/workflows/wai-web-wasm.yml b/.github/workflows/wai-web-wasm.yml new file mode 100644 index 000000000..793c6fdc4 --- /dev/null +++ b/.github/workflows/wai-web-wasm.yml @@ -0,0 +1,95 @@ +name: wai-web wasm32 + +# The browser sink (wai/wai-web) is the same Rust as the native reference, +# compiled to wasm32-unknown-unknown. This job builds it and runs, under node: +# +# - demo/verify_int_synth_wasm.mjs against the committed conformance corpus +# wai/neural-conformance/ (not the demo's staged copies): the wasm32 +# int_synth decode must hash to the corpus's frozen native hashes; +# - demo/verify_menu.mjs: every capability the browser sink advertises is +# registered in wai/registry/capabilities.json as a payload or companion +# capability, none is a candidate (SPEC §5), and the wasm build's own +# registry agrees with the export on every candidate; +# - demo/verify_pack.mjs: the browser writers `pack` and `packMulti` refuse +# every candidate the export lists, in every place an envelope can name one; +# - a byte comparison of the demo's staged int_synth copies with the corpus. +# +# The full-scale int_hyper wasm decode needs vectors regenerated from a +# pretrained model; wai-sota-vectors.yml runs it after regenerating them. + +on: + push: + branches: [main] + paths: + - 'wai/wai-web/**' + - 'wai/wai-rs/**' + - 'wai/wai-quantum/**' + - 'wai/neural-conformance/**' + - 'wai/registry/**' + - '.github/workflows/wai-web-wasm.yml' + pull_request: + paths: + - 'wai/wai-web/**' + - 'wai/wai-rs/**' + - 'wai/wai-quantum/**' + - 'wai/neural-conformance/**' + - 'wai/registry/**' + - '.github/workflows/wai-web-wasm.yml' + +jobs: + wasm32: + name: wai-web wasm32 build · corpus, menu and writers + runs-on: linux + timeout-minutes: 45 + steps: + - uses: actions/checkout@v5 + # The docker runner image ships no Rust. + - name: Install Rust 1.98.0 with the wasm32 target + shell: bash + run: | + export PATH="$HOME/.cargo/bin:$PATH" + command -v rustup >/dev/null 2>&1 || \ + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0 + rustup toolchain install 1.98.0 --profile minimal + rustup default 1.98.0 + rustup target add wasm32-unknown-unknown + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + # Pinned to the wasm-bindgen version in wai-web/Cargo.lock: the CLI and the + # crate must match exactly. + - name: wasm-bindgen CLI 0.2.127 + shell: bash + run: cargo install --locked wasm-bindgen-cli --version 0.2.127 + - uses: actions/setup-node@v4 + with: + node-version: '22' + - name: Build wai-web for wasm32 (node bindings) + shell: bash + working-directory: wai/wai-web + run: | + set -euo pipefail + cargo build --locked --release --target wasm32-unknown-unknown + wasm-bindgen --target nodejs --out-dir demo/pkg-node --out-name wai_web \ + target/wasm32-unknown-unknown/release/wai_web.wasm + - name: wasm32 int_synth decode equals the frozen native corpus + shell: bash + working-directory: wai/wai-web + run: node demo/verify_int_synth_wasm.mjs ./pkg-node/wai_web.js ../neural-conformance + - name: The browser menu is registered and names no candidate + shell: bash + working-directory: wai/wai-web + run: node demo/verify_menu.mjs ./pkg-node/wai_web.js ../registry/capabilities.json + - name: The browser writers refuse every candidate + shell: bash + working-directory: wai/wai-web + run: node demo/verify_pack.mjs ./pkg-node/wai_web.js ../registry/capabilities.json + - name: The demo's staged int_synth vectors are byte copies of the corpus + shell: bash + working-directory: wai + run: | + set -euo pipefail + cmp wai-web/demo/int_synth/expected_hashes.json neural-conformance/expected_hashes.json + for n in gradient bloom facade photo; do + for f in latent.bin int_synth.json; do + cmp "wai-web/demo/int_synth/$n/$f" "neural-conformance/vectors/$n/$f" + done + done diff --git a/scripts/check-comparative-prose.sh b/scripts/check-comparative-prose.sh new file mode 100755 index 000000000..3914e98e0 --- /dev/null +++ b/scripts/check-comparative-prose.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +# +# check-comparative-prose.sh +# +# Holds WAI's public text to its writing rule: it states what WAI does, +# plainly, and does not rank WAI against anything else. The phrases below are +# the comparative and promotional forms the tree has had to remove; each is a +# POSIX extended regular expression, matched case-sensitively. +# +# Scope: the same files as scripts/check-public-names.sh — the tracked files +# under wai/ with the extensions .md .rs .toml .py .sh .mjs .js .json .html +# .css .ttl .h .c .txt and wai/'s .gitignore files, except the settled OER/2 +# documents and corpus, and except wai/CHANGELOG.md: its history is a record, +# and only names are redacted from it. Files given as arguments replace that +# list (the self-test, scripts/test-public-text-checks.sh, uses this). +# +# Opt-out, for a functional identifier or a quotation only (an environment +# variable or file name that contains a listed word, a phrase quoted from a +# cited document): put `allow-phrase: ` on the same line, in a comment +# of the file's own syntax. The reason is required: it must contain a letter or +# a digit. +# +# Exit status: 0 clean; 1 a hit; 2 a pattern grep cannot compile, or any other +# grep error. The check never reads an error as "clean". + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +PHRASES=( + '\bSOTA\b' + '\bwin vs\b' + '\b[Mm]oat\b' + '[Nn]obody signs' + '[Nn]ot one signs' + '[Nn]one signs' + '[Bb]est[- ]in[- ]class' + '\b[Bb]eats\b [A-Z]' + '\b[Ss]uperior(ity)?\b' + '% better' + '\b[Tt]he opposite\b' + '[Ss]trictly worse' + '[Tt]oll[- ]road' + '\b[Pp]arity \+' + '[Ff]eature[- ]parity' + '\bparity with\b' + '\b[Uu]nderperforms\b' + '\b[Oo]utperforms\b' + 'royalty trap' + 'well below [A-Z]' + 'well above [A-Z]' + 'among the (lowest|best|highest|fastest|smallest)' + 'nothing else .* offers?' + '\b[Nn]o other .* (offers?|signs?|provides?)' + '[Cc]ommercial wedge' + '\b[Uu]nclaimed\b' + '\b[Ff]ast win\b' + '\b[Dd]ifferentiator\b' + '[Rr]ate-competitive' +) + +args=() +for p in "${PHRASES[@]}"; do args+=(-e "$p"); done +# Compile every phrase once before scanning: grep exits 1 when nothing matches +# and 2 on an error, and only 0 and 1 mean the list is usable. +set +e +grep -E "${args[@]}" /dev/null >/dev/null +rc=$? +set -e +if [ $rc -gt 1 ]; then + echo "::error::a phrase in $0 does not compile (grep exit $rc); nothing was checked" >&2 + exit 2 +fi + +files=() +if [ $# -gt 0 ]; then + files=("$@") +else + while IFS= read -r f; do + case "$f" in + wai/extensions/oer-receipts.md|wai/wai-rs/src/oer.rs|wai/oer-conformance/*) continue ;; + wai/CHANGELOG.md) continue ;; + esac + files+=("$f") + done < <(git ls-files 'wai/*.md' 'wai/*.rs' 'wai/*.toml' 'wai/*.py' 'wai/*.sh' 'wai/*.mjs' 'wai/*.js' \ + 'wai/*.json' 'wai/*.html' 'wai/*.css' 'wai/*.ttl' 'wai/*.h' 'wai/*.c' 'wai/*.txt' \ + 'wai/*.gitignore') +fi +if [ ${#files[@]} -eq 0 ]; then + echo "::error::no file to check" >&2 + exit 2 +fi + +out="$(mktemp)" +trap 'rm -f "$out"' EXIT +set +e +grep -HnE "${args[@]}" -- "${files[@]}" > "$out" +rc=$? +set -e +if [ $rc -gt 1 ]; then + echo "::error::grep failed (exit $rc) while scanning; nothing was checked" >&2 + exit 2 +fi + +hits=0 +while IFS= read -r line; do + content="${line#*:}" + content="${content#*:}" + if printf '%s' "$content" | grep -qE 'allow-phrase:[[:space:]]*[[:alnum:]]'; then + continue + fi + if [ $hits -eq 0 ]; then + echo "::error::comparative or promotional phrasing in public text (scripts/check-comparative-prose.sh)" + fi + echo "$line" + hits=$((hits + 1)) +done < "$out" + +if [ $hits -gt 0 ]; then + echo "Fix: state what WAI does, measured where a figure is given, without ranking it against" + echo "anything else; for an identifier or a quotation, mark the line with allow-phrase: ." + exit 1 +fi +echo "comparative-prose check: clean (${#files[@]} files)" diff --git a/scripts/check-public-names.sh b/scripts/check-public-names.sh new file mode 100755 index 000000000..365b0e006 --- /dev/null +++ b/scripts/check-public-names.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# +# check-public-names.sh +# +# Holds WAI's public text to its naming rule: it names no company, vendor, +# customer or commercial product. Standards, standards bodies, protocols, +# papers and open-source projects may be named, factually. +# +# The patterns are NOT in this repository. They are read from the +# environment variable WAI_NAME_DENYLIST: newline-separated POSIX extended +# regular expressions, matched case-sensitively (write `[Ff]oo` where both +# cases matter). In CI the variable comes from a forge Actions secret of the +# same name; locally, export it from a private file before committing: +# +# WAI_NAME_DENYLIST="$(cat /path/to/private/patterns)" scripts/check-public-names.sh +# +# A public list would itself publish the names it exists to keep out. +# +# Scope: the tracked files under wai/ with the extensions .md .rs .toml .py +# .sh .mjs .js .json .html .css .ttl .h .c .txt, and wai/'s .gitignore files; +# or, when files are given as arguments, those files (the self-test, +# scripts/test-public-text-checks.sh, uses this). Excluded: +# - the settled OER/2 documents and corpus (wai/extensions/oer-receipts.md, +# wai/wai-rs/src/oer.rs, wai/oer-conformance/), which no change may edit. +# +# Opt-out, for a functional identifier only (a model-hub id a tool defaults +# to, a repository URL, an OS API name, a security advisory id, a capability +# string): put `allow-name: ` on the same line, in a comment of the +# file's own syntax (``, `// allow-name: reason`, +# `# allow-name: reason`). The reason is required: it must contain a letter or +# a digit. Prose around such an identifier describes the standard or the +# function, not the owner. +# +# Exit status: 0 clean; 1 a hit; 2 WAI_NAME_DENYLIST unset or empty, a pattern +# grep cannot compile, or any other grep error. The check never reads an error +# as "clean". + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +if [ -z "${WAI_NAME_DENYLIST:-}" ]; then + echo "WAI_NAME_DENYLIST is not set: nothing to check against (see the header of $0)" >&2 + exit 2 +fi + +patterns="$(mktemp)" +out="$(mktemp)" +trap 'rm -f "$patterns" "$out"' EXIT +printf '%s\n' "$WAI_NAME_DENYLIST" | grep -v '^[[:space:]]*$' > "$patterns" || true +if [ ! -s "$patterns" ]; then + echo "WAI_NAME_DENYLIST holds no pattern" >&2 + exit 2 +fi +# Compile every pattern once before scanning. grep exits 1 when nothing matches +# and 2 on an error; only 0 and 1 mean the patterns are usable. grep's message +# is suppressed: it could quote a private pattern. +set +e +grep -E -f "$patterns" /dev/null >/dev/null 2>&1 +rc=$? +set -e +if [ $rc -gt 1 ]; then + echo "::error::WAI_NAME_DENYLIST holds a pattern grep cannot compile (grep exit $rc); nothing was checked" >&2 + exit 2 +fi + +files=() +if [ $# -gt 0 ]; then + files=("$@") +else + while IFS= read -r f; do + case "$f" in + wai/extensions/oer-receipts.md|wai/wai-rs/src/oer.rs|wai/oer-conformance/*) continue ;; + esac + files+=("$f") + done < <(git ls-files 'wai/*.md' 'wai/*.rs' 'wai/*.toml' 'wai/*.py' 'wai/*.sh' 'wai/*.mjs' 'wai/*.js' \ + 'wai/*.json' 'wai/*.html' 'wai/*.css' 'wai/*.ttl' 'wai/*.h' 'wai/*.c' 'wai/*.txt' \ + 'wai/*.gitignore') +fi +if [ ${#files[@]} -eq 0 ]; then + echo "::error::no file to check" >&2 + exit 2 +fi + +set +e +grep -HnE -f "$patterns" -- "${files[@]}" > "$out" 2>/dev/null +rc=$? +set -e +if [ $rc -gt 1 ]; then + echo "::error::grep failed (exit $rc) while scanning; nothing was checked" >&2 + exit 2 +fi + +hits=0 +while IFS= read -r line; do + content="${line#*:}" + content="${content#*:}" + if printf '%s' "$content" | grep -qE 'allow-name:[[:space:]]*[[:alnum:]]'; then + continue + fi + # Print the location only: the matched text may be a name the list keeps private. + if [ $hits -eq 0 ]; then + echo "::error::public text names a company, vendor, customer or product (patterns: WAI_NAME_DENYLIST)" + fi + echo "${line%%:*}:$(printf '%s' "${line#*:}" | cut -d: -f1)" + hits=$((hits + 1)) +done < "$out" + +if [ $hits -gt 0 ]; then + echo "Fix: name the standard, the standards body, the open-source project or the function instead;" + echo "for a functional identifier, mark the line with allow-name: ." + exit 1 +fi +echo "public-name check: clean (${#files[@]} files)" diff --git a/scripts/test-public-text-checks.sh b/scripts/test-public-text-checks.sh new file mode 100755 index 000000000..85aa775c8 --- /dev/null +++ b/scripts/test-public-text-checks.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# +# test-public-text-checks.sh +# +# Self-test of scripts/check-public-names.sh and scripts/check-comparative-prose.sh. +# Each case feeds a check a fixture file (or a pattern list) and states the exit +# status it must give: +# - a hit exits 1, a clean file exits 0; +# - an opt-out with a reason is honoured, and one with an empty reason (the +# comment form ``, whose `-->` must not count as the +# reason) is not; +# - a pattern grep cannot compile exits 2, never "clean". +# The fixture names a made-up company, never a real one. +# +# Exit status: 0 when every case gives its stated status, 1 otherwise. + +set -uo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +names="$REPO_ROOT/scripts/check-public-names.sh" +prose="$REPO_ROOT/scripts/check-comparative-prose.sh" +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT + +failures=0 +expect() { # expect