Replay Nightly #165
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: Replay Nightly | |
| # The device replay suites this workflow used to carry moved to replays-manual.yml (#1781 A1) | |
| # after failing every scheduled run from 2026-07-24 on. What stays here is what a schedule can | |
| # still keep honest: the device-free parser fuzz lane. | |
| on: | |
| schedule: | |
| - cron: '0 3 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| fuzz-iterations: | |
| description: 'Parser fuzz cases per target' | |
| required: false | |
| default: '38000' | |
| fuzz-seed: | |
| description: 'Parser fuzz PRNG seed (defaults to the run number)' | |
| required: false | |
| permissions: | |
| contents: read | |
| actions: read | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # Parser fuzz lane (#1414, validation targets #1781 B2): hostile input into parseArgs, | |
| # selector parsing, .ad replay scripts, batch --steps JSON, and the Maestro compat parser, | |
| # plus schema-derived CLI/Maestro validation cases that assert a planted outcome (specific | |
| # error code, or acceptance) so a silent acceptance is a finding too. Every rejection must | |
| # be a typed AppError with a non-empty hint, and no case hangs. Device-free, so it rides | |
| # this nightly rather than owning a workflow; the seed varies per run so the lane keeps | |
| # exploring, and anything it catches is appended to the checked-in corpus that the unit | |
| # lane replays (scripts/fuzz/corpus/regressions.json). | |
| # | |
| # 38,000 cases/target holds the job's wall-clock at the pre-B2 five-target/50k budget now that | |
| # seven targets share it: measured on a quiet host, 7x38k = 16.5s against 5x50k = 16.6-16.9s. | |
| # Chosen by measurement rather than rounding — 33k would have been 2s cheaper but cost the | |
| # five untouched targets a third of their depth for no wall-clock reason. | |
| nightly-parser-fuzz: | |
| name: Parser Fuzz Lane | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Setup toolchain | |
| uses: ./.github/actions/setup-node-pnpm | |
| # A lane whose classifier or watchdog regressed would pass forever. This runs the | |
| # broken-on-purpose targets first and fails unless each violation is caught. | |
| - name: Self-check the harness | |
| uses: ./.github/actions/run-gate | |
| with: | |
| gate: fuzz-parsers | |
| args: | | |
| --self-check | |
| --artifact-dir | |
| .tmp/fuzz/self-check | |
| # Runs even when the self-check fails, so the lane always produces a fuzz envelope too | |
| # (a step that never runs writes no envelope, and monitoring cannot tell that apart from | |
| # a lane that went dark). The job still fails because both steps report their status. | |
| - name: Fuzz parsers | |
| if: always() | |
| env: | |
| FUZZ_ITERATIONS: ${{ github.event.inputs.fuzz-iterations || '38000' }} | |
| FUZZ_SEED: ${{ github.event.inputs.fuzz-seed || github.run_number }} | |
| uses: ./.github/actions/run-gate | |
| with: | |
| gate: fuzz-parsers | |
| args: | | |
| --iterations | |
| ${{ env.FUZZ_ITERATIONS }} | |
| --seed | |
| ${{ env.FUZZ_SEED }} | |
| --artifact-dir | |
| .tmp/fuzz/run | |
| # Uploaded on pass as well as failure: each subdirectory of .tmp/fuzz holds a | |
| # run-envelope.json on #1430's shared lane contract (scripts/lib/lane-envelope.ts) — | |
| # commit/ref/run provenance, tool + config hash, seed, result — which is what freshness | |
| # monitoring reads. | |
| - name: Upload run envelopes and failing cases | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: parser-fuzz-run-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: .tmp/fuzz | |
| if-no-files-found: warn | |
| # Never fails the job on its own: a missing envelope is reported as such rather than | |
| # masking the real failure with a `cat` error. | |
| - name: Summarize | |
| if: always() | |
| run: | | |
| { | |
| echo '### Parser fuzz lane' | |
| found=0 | |
| for envelope in .tmp/fuzz/*/run-envelope.json; do | |
| [ -f "$envelope" ] || continue | |
| found=1 | |
| echo "#### $envelope" | |
| echo '```json' | |
| cat "$envelope" | |
| echo '```' | |
| done | |
| [ "$found" = 1 ] || echo 'No run envelope was produced — the lane failed before it could run.' | |
| } >> "$GITHUB_STEP_SUMMARY" |