Skip to content

perf(pulsar): elide idle PreviewReturn copies #442

perf(pulsar): elide idle PreviewReturn copies

perf(pulsar): elide idle PreviewReturn copies #442

Workflow file for this run

# Pulsar compliance gates -- étage-0 merge-gate conformance.
#
# These are the conformance checks required by the org-wide merge gate
# (docs/rules/git.md §1 + docs/rules/security.md §Détection). The build
# pipeline (pipeline.yml) proves pulsar.exe builds and broadcasts; this
# workflow proves the change is mergeable per policy: no leaked secret,
# no high/critical dependency CVE, a lockfile that is in sync, and a
# valid CODEOWNERS.
#
# Kept in a SEPARATE workflow from pipeline.yml on purpose:
# - different concern (governance vs build), different runners (all
# ubuntu, cheap + fast -- no MSVC, no Windows),
# - independent failure surface : a red secret-scan must not be tangled
# with the C++ build graph, and vice versa. Each job is its own check
# in `gh pr checks` so the reviewer sees exactly which gate broke.
#
# HARD RULE : no error-suppression anywhere (the step-skip toggle is
# banned per docs/rules/git.md). Every job here is allowed -- and intended
# -- to turn the PR red and block the merge.
#
# Trigger parity with pipeline.yml : pull_request to main fires once per
# push (the dedup reason pipeline.yml documents), plus push to main and
# tags so post-merge / release refs are re-checked. paths-ignore mirrors
# pipeline.yml: a docs-only / changelog / licence change can't introduce
# a secret-in-code, a dep CVE, a lockfile drift or a CODEOWNERS break.
name: compliance
on:
push:
branches: [main]
tags:
- 'v*.*.*'
paths-ignore:
- '**/*.md'
- 'docs/**'
- 'CHANGELOG.md'
- '.gitignore'
- 'LICENSE'
pull_request:
branches: [main]
paths-ignore:
- '**/*.md'
- 'docs/**'
- 'CHANGELOG.md'
- '.gitignore'
- 'LICENSE'
concurrency:
group: compliance-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
# ── secret-scan : trufflehog (filesystem + git history) + detect-secrets ──
# Two complementary scanners :
# - trufflehog scans the full git history of the diff range (PR : base
# ..head ; push : the pushed range) AND the working tree, with
# --only-verified so a finding is a credential that actually
# authenticates somewhere -- not an entropy false-positive.
# - detect-secrets audits the working tree against .secrets.baseline.
# Any NEW finding not already in the baseline fails the job; this is
# the entropy/keyword net trufflehog's verified-only mode skips.
# Either scanner flagging a real/new secret turns the job red. A leaked
# secret => rotate + purge (security.md), never just revert.
secret-scan:
name: secret-scan
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout (full history)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: TruffleHog (history + filesystem, verified secrets)
uses: trufflesecurity/trufflehog@main
with:
# PR : scan base..head. push : the action infers the pushed
# range from the event. Defaulting both to the repo path also
# scans the checked-out working tree.
path: ./
base: ${{ github.event.pull_request.base.sha || github.event.before }}
head: ${{ github.event.pull_request.head.sha || github.sha }}
extra_args: --only-verified
- name: Setup Python 3.11
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install detect-secrets
run: python -m pip install --upgrade "detect-secrets==1.5.0"
- name: detect-secrets audit against baseline
# Re-scan the tree against the committed baseline, then compare ONLY
# the `results` block (the actual findings) -- NOT the whole file --
# against what is committed. `scan --baseline` rewrites the file in
# place and always refreshes the volatile `generated_at` timestamp
# (and may reorder plugin metadata), so a raw `git diff` on the file
# would fail on the timestamp alone with zero new secrets. By diffing
# the `results` map alone we ignore that noise and fail only when a
# finding appears that is not already in the audited baseline -- i.e.
# a genuinely new secret-shaped string. To add a legitimate new
# allowlist entry: `detect-secrets scan > .secrets.baseline`, audit,
# commit. A real secret => ROTATE + purge per security.md.
run: |
set -euo pipefail
# Snapshot the committed baseline OUTSIDE the repo tree so the
# re-scan below doesn't pick the copy up as a new file.
cp .secrets.baseline "${RUNNER_TEMP}/baseline.committed.json"
detect-secrets scan --baseline .secrets.baseline
python - <<PY
import json, os, sys
def results(path):
with open(path) as f:
return json.load(f).get("results", {})
before = results(os.path.join(os.environ["RUNNER_TEMP"], "baseline.committed.json"))
after = results(".secrets.baseline")
if before != after:
print("::error::detect-secrets found findings not present in the audited .secrets.baseline.")
before_keys = {(fn, x["hashed_secret"]) for fn, lst in before.items() for x in lst}
after_keys = {(fn, x["hashed_secret"]) for fn, lst in after.items() for x in lst}
for fn, h in sorted(after_keys - before_keys):
print(f"::error::NEW finding in {fn} (hash {h[:12]}...). Review: real secret => rotate+purge; false positive => re-baseline.")
for fn, h in sorted(before_keys - after_keys):
print(f"::warning::baseline finding no longer present in {fn} (hash {h[:12]}...). Re-baseline to prune.")
sys.exit(1)
print("detect-secrets: no findings beyond the audited baseline.")
PY
# ── deps-audit : npm high/critical CVE gate ──────────────────────────
# Dependency scope = npm. The repo's runtime deps are the @clodocapeo/
# pulsar-* JS packages (package.json + package-lock.json). The Python
# side is dev-only probe glue (`websockets`), not a shipped/pinned
# dependency surface, so there is no requirements lock to pip-audit --
# the deps gate that matters for what ships is npm. --omit=dev so the
# gate reflects what consumers actually install (dev-only advisories,
# e.g. the vitest test toolchain, do not block a merge). --audit-level
# =high : moderate/low advise but do not block; high+critical block.
deps-audit:
name: deps-audit
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: npm audit (production deps, high+critical block)
run: npm audit --omit=dev --audit-level=high
# ── lockfile-check : package-lock.json is in sync + committed ────────
# `npm ci` refuses to run if package.json and package-lock.json are out
# of sync, so `npm ci --dry-run` is the canonical "lockfile drift"
# detector -- it errors on any divergence or unpinned dependency without
# touching node_modules. We then assert the working tree is still clean
# so a lockfile that gets rewritten on install (e.g. a non-deterministic
# or stale lock) is caught as an uncommitted change.
lockfile-check:
name: lockfile-check
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Assert no stray yarn.lock (npm is the lock of record)
run: |
set -euo pipefail
if [ -f yarn.lock ]; then
echo "::error::yarn.lock present but Pulsar uses npm (package-lock.json) as the lock of record. Remove yarn.lock or migrate intentionally."
exit 1
fi
echo "no yarn.lock -- npm is the sole lockfile."
- name: npm ci --dry-run (lockfile in sync, deps pinned)
# --force : @clodocapeo/pulsar-bundle declares os:["win32"]
# cpu:["x64"], so on the ubuntu runner npm aborts with
# EBADPLATFORM before it ever checks lockfile sync. --force
# bypasses the os/cpu gate (same reason npm-publish in
# pipeline.yml uses it) WITHOUT weakening the sync check: a real
# lockfile drift surfaces as a distinct EUSAGE/"can only install
# with an up to date package-lock" error, not EBADPLATFORM.
env:
PULSAR_BUNDLE_SKIP_POSTINSTALL: '1'
run: npm ci --dry-run --force
- name: Assert lockfile unchanged after resolution
run: |
set -euo pipefail
if ! git diff --quiet -- package-lock.json; then
echo "::error::package-lock.json drifted during resolution -- regenerate with 'npm install' and commit."
git --no-pager diff -- package-lock.json
exit 1
fi
echo "package-lock.json is in sync and committed."
# ── codeowners-check : CODEOWNERS exists and is syntactically valid ──
# A merge gate that relies on CODEOWNERS for review routing is only as
# good as the file being present + parseable. We assert it exists, then
# lint it with the same parser semantics GitHub uses (pattern + at least
# one @owner / team / email per non-comment line). No external network
# call -- the validation is purely structural.
codeowners-check:
name: codeowners-check
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Locate + validate CODEOWNERS
run: |
set -euo pipefail
file=""
for cand in CODEOWNERS .github/CODEOWNERS docs/CODEOWNERS; do
if [ -f "$cand" ]; then file="$cand"; break; fi
done
if [ -z "$file" ]; then
echo "::error::No CODEOWNERS found (looked in /, /.github, /docs)."
exit 1
fi
echo "Validating $file"
# disable pathname expansion : CODEOWNERS patterns like '*' or
# '/plugins/*' must be treated literally, not glob-expanded
# against the runner's working tree by the unquoted `set --`.
set -f
fail=0
lineno=0
while IFS= read -r line || [ -n "$line" ]; do
lineno=$((lineno+1))
# strip leading/trailing whitespace
trimmed="$(echo "$line" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
# skip blank + comment lines
[ -z "$trimmed" ] && continue
case "$trimmed" in \#*) continue;; esac
# a rule line is: <pattern> <owner> [<owner>...]
# every token after the first must be @user, @org/team, or an email
set -- $trimmed
pattern="$1"; shift
if [ "$#" -eq 0 ]; then
echo "::error::$file:$lineno: rule '$pattern' has no owner."
fail=1; continue
fi
for owner in "$@"; do
case "$owner" in
@*/*) : ;; # @org/team
@*) : ;; # @user
*@*.*) : ;; # email
*)
echo "::error::$file:$lineno: invalid owner token '$owner' (expected @user, @org/team, or email)."
fail=1 ;;
esac
done
done < "$file"
[ "$fail" -eq 0 ] || exit 1
echo "CODEOWNERS is syntactically valid."