Files
codebase-memory-mcp/.github/workflows/dry-run.yml
T
Martin Vogel 453b21e680 ci(codeql): PR scan per language inside PR CI, full scan on main
Bring CodeQL back to pull requests as jobs of PR CI itself (needed by
ci-ok, no polling), narrowed to first-party code, while push to main,
the weekly run, the dry run and the release keep the full scan.

- codeql.yml: one leg per language (c-cpp, javascript-typescript,
  python, actions), scope in .github/codeql/codeql-config.yml. The full
  scan traces the whole build, vendored C included. The PR scan
  (light: true) analyses each selected language's whole first-party
  scope; c-cpp compiles the third-party objects before tracing
  (scripts/build.sh --vendored-only) and then traces the first-party
  build plus the vendored files the PR changes (--reuse-vendored
  --retrace-from, make -W). Diff-informed analysis (codeql-action
  v4.38.1, forced on for PR calls) only restricts reported alerts.
  The alert check moves into each leg and reads the analysed ref.
  Push and PR runs cancel superseded runs per language; dispatched
  runs never cancel. Compiler cache off so every compile is traced.
- pr.yml: a codeql job runs the legs of the selected codeql-* lanes;
  ci-ok needs it.
- select-lanes.sh: codeql-cpp/-js/-py/-actions lanes, selected by a
  change in their scope (any C/C++ incl. vendored; JS/TS, Python and
  workflows inside the config's paths minus paths-ignore) and by every
  full run (undeterminable diff, workflows, build, CodeQL machinery).
  --codeql-scope LANGUAGE prints a list's in-scope paths.
- codeql-retrace-vendored.sh: the vendored C files a PR changes (empty,
  never narrowing, when the diff cannot be determined).
- Makefile.cbm: cbm-third-party builds the vendored prod objects only.
- tests: the gate contract pins the new layout; the selector contract
  pins that a C change always selects the C scan, an undeterminable
  diff selects every language, and a vendored-only change retraces
  exactly the changed vendored files. Selector fixtures, ci-ok's job
  map, the replay's codeql-action failures (lane codeql-cpp) and the
  interface probes follow.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-20 23:09:09 +02:00

165 lines
6.7 KiB
YAML

# Manual trigger: test everything before pushing a release.
# Each step can be skipped for faster iteration.
#
# Pipeline: lint → test → build + candidate scan/select → smoke/soak
# Security: security-static + license-gate (independent island)
# CodeQL: codeql.yml full scan of this commit + alert check (independent island)
#
# Security and CodeQL do NOT block lint/test/build/smoke/soak. All jobs must pass
# for the overall workflow to be green.
#
# Candidate VirusTotal scanning and tuple-local selection are part of the
# reusable build boundary. Smoke and soak therefore start only after all 16
# stripped/unstripped candidates have completed and the selected exact bytes
# have been packaged under the canonical artifact names.
#
# The skips are independent because they answer different questions:
# skip_builds no artifacts at all "do lint and tests pass?"
# skip_smoke build + scan/select, no smoke "does this diff scan red?"
# skip_virustotal default-stripped + smoke, no scan "do the archives pass CI?"
#
# skip_virustotal is a conspicuous dry-run-only bypass for API-quota-sensitive
# iteration. It records unscanned-dry-run and deterministically chooses stripped
# within each tuple. release.yml has no corresponding bypass.
name: Dry Run
on:
workflow_dispatch:
inputs:
skip_lint:
description: 'Skip lint (cppcheck + clang-format)'
type: boolean
default: false
skip_tests:
description: 'Skip unit/integration tests'
type: boolean
default: false
skip_builds:
description: 'Skip build + smoke (also skips VirusTotal — nothing to scan)'
type: boolean
default: false
skip_smoke:
description: 'Skip smoke (candidate scan/select still runs inside build)'
type: boolean
default: false
skip_virustotal:
description: 'Skip candidate scan; mark unscanned and default to stripped'
type: boolean
default: false
skip_memwaste:
description: 'Skip the waste sanitizer (report-only) and the fuzz smoke'
type: boolean
default: false
soak_level:
description: 'Soak: full (quick+asan), quick (10min), none'
type: choice
options: ['full', 'quick', 'none']
default: 'quick'
permissions:
contents: read
jobs:
# ── Security (independent island — does not block main pipeline) ──
security:
uses: ./.github/workflows/_security.yml
# No job in _security.yml reads code scanning or the Actions API any more
# (the CodeQL gate moved to codeql.yml), so the token stays read-only.
permissions:
contents: read
secrets: inherit
# ── CodeQL: full scan of this exact commit + the open-alert check ──
# Every language, vendored C included (PRs run the narrower PR scan).
# codeql.yml uploads results and reads the alerts: a called workflow
# cannot request more than its caller grants, so withholding either
# permission fails the whole run at startup.
codeql:
uses: ./.github/workflows/codeql.yml
permissions:
contents: read
security-events: write
actions: read
# ── Lint (cppcheck + clang-format) ────────────────────────────
lint:
if: ${{ inputs.skip_lint != true }}
uses: ./.github/workflows/_lint.yml
# ── Tests (all platforms, perf tests skipped on CI) ────────────
test:
needs: [lint]
if: ${{ inputs.skip_tests != true && !cancelled() && (needs.lint.result == 'success' || needs.lint.result == 'skipped') }}
uses: ./.github/workflows/_test.yml
with:
skip_perf: true
broad_platforms: true
shard_suites: true
# ── Build all platforms ────────────────────────────────────────
build:
if: ${{ inputs.skip_builds != true && !cancelled() && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
needs: [test]
permissions:
contents: read
id-token: write
attestations: write
uses: ./.github/workflows/_build.yml
with:
attest: false
scan_candidates: ${{ !inputs.skip_virustotal }}
secrets: inherit
# ── Smoke test every binary ────────────────────────────────────
# Run unless builds were skipped or a build leg FAILED. Every build leg
# (including the macos-15-intel darwin-amd64 binary) is blocking now, so a
# failed/missing platform binary correctly stops smoke — see _build.yml.
smoke:
if: ${{ inputs.skip_builds != true && inputs.skip_smoke != true && !cancelled() && needs.build.result != 'failure' && needs.build.result != 'skipped' }}
needs: [build]
uses: ./.github/workflows/_smoke.yml
with:
broad_platforms: true
# ── Waste sanitizer, REPORT-ONLY (independent island) ─────────
# Event lane on Linux, macOS and Windows, access lane and the per-function
# scaling lane on Linux. Uploads reports; fails only when the measurement is
# unsound (a defect of the tool itself).
memwaste:
if: ${{ inputs.skip_memwaste != true }}
uses: ./.github/workflows/_memwaste.yml
with:
mode: full
# ── Fuzz smoke (independent island) ───────────────────────────
# A fixed number of executions from a fixed seed per target, so the verdict is
# a function of the commit (O9). A crash here is a real defect in a parser
# that reads untrusted input; its reproducer is uploaded.
fuzz:
if: ${{ inputs.skip_memwaste != true }}
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install deps
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev clang llvm libclang-rt-dev
- name: Fuzz smoke (extract, cypher, config)
run: scripts/fuzz.sh all --runs 20000 --seed 1
- name: Upload reproducers
if: ${{ failure() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: fuzz-artifacts
path: build/fuzz/artifacts-*/
if-no-files-found: ignore
# ── Soak tests (optional, parallel with smoke) ────────────────
soak:
if: ${{ inputs.soak_level != 'none' && !cancelled() && needs.build.result != 'failure' && needs.build.result != 'skipped' }}
needs: [build]
uses: ./.github/workflows/_soak.yml
with:
duration_minutes: 10
run_asan: ${{ inputs.soak_level == 'full' }}
use_release_artifacts: true