Repository navigation
Conversation
Phase 0 of the CI Rewrite 1.2 (Refs #479): introduce the single CI engine and its inputs as the foundation for the orchestrator model. - .github/yaml/build-manifest.yml: single source of truth (base image digests, external tool versions, build matrix, release inventory, impact classes); full-length SHAs only. - .github/scripts/ci.sh: fail-closed CI_COMMANDS dispatch, awk-only SOT readers, nproc*2 (min 16) bats job count, resolve, and the content-based impact phase (DEFAULT=NOOP; a docs-only diff selects doc-lint, never a compile). Remaining phases fail closed. - .github/scripts/ci.bats: regression suite for dispatch, SOT readers, parallelism floor, resolve, and impact classification. WIP: legacy CI untouched; no workflows rewired yet.
Phase 0 (Refs #479): first two governance guards, run by the `lint` phase and covered green+red by ci.bats fixtures. - ci_guard_line_endings: fails closed on any CR byte (LF-only repo). - ci_guard_full_sha: fails closed on any sha256 digest that is not full 64-hex, or any `uses:` action pin that is not a full 40-hex SHA. - ci_cmd_lint wires both; full-SHA scans only pin-bearing trees (.github/workflows|actions|yaml, docker), not .github/scripts, since ci.sh carries no pins by design and ci.bats holds deliberate fixtures. Real-tree lint is clean; both guards proven on green and red fixtures.
Phase 0 (Refs #479): bind Dependabot's Dockerfile digest bumps to the SOT so the two cannot drift. - ci_guard_dependabot_consistency: fails closed if a base_images pin in build-manifest.yml is not present verbatim in its Dockerfile (debian_verify/golang_actionlint -> docker/verify/Dockerfile, debian_release -> docker/release/Dockerfile). A Dependabot bump that does not reach the SOT turns the lint phase red. - Wired into ci_cmd_lint; real-tree lint clean, guard proven green+red on self-contained SOT+Dockerfile fixtures.
Phase 0 (Refs #479): forbid inline logic in workflow run: blocks; a run: step calls one command (bash ci.sh <phase>) only. - _ci_scan_run_blocks / ci_guard_orchestrator_only: flag control-flow keywords, &&/||/;, pipes, command/backtick substitution, heredocs, bash -c / python -c, awk/sed/jq, and set -e/-u/-o inside run: blocks. - ci_cmd_lint scans every .github/workflows/*.yml; there is no legacy exemption -- every shipped workflow is an orchestrator. - Proven green+red on fixtures. Against the current tree it reports 285 inline-logic violations across 12 not-yet-rewritten legacy workflows; these clear as each workflow is rewritten and removed.
Phase 1 (Refs #479): fold c-build.yml's per-variant build commands into ci.sh so a workflow runs `ci.sh build <variant>` only. NOT yet verified in the buildtools container (real build/test runs are container-only, rule 87); this commit is the faithful command fold. - ci_cmd_build: default (ccache CC + python3.13||python3), popt-fallback (asserts the bundled-popt fallback in configure.log), popt-vendor (--without-system-popt + standalone Werror gcc compile of popt/*.c), coverage (--coverage -O0 --with-seccomp), sanitizer (ASan/UBSan flags, --without-seccomp). Unknown variant fails closed before autogen. - _ci_has_compiler_warning: warnings-are-errors (rule 31), anchored to the gcc/clang diagnostic line shape (catches the non-Werror include-server setup.py path). - _ci_popt_strict_compile: the vendored-popt strict compile. - ci.bats: unknown-variant rejection (green build path is container-only).
Phase 1 (Refs #479): fold run-tests.sh's comfychair verdict and c-build.yml's per-variant make-check env into ci.sh. Real make-check runs are container-only (rule 87); this is the faithful fold. - _ci_parse_comfychair: OK/NOTRUN/FAIL parse; 0/0/0 parsed is a hard fail (rule 66); any FAIL fails. - ci_cmd_test: default/popt-fallback (plain), sanitizer (ASAN/UBSan options), coverage (coverage-python-wrapper + lcov capture), popt-vendor (no make-check phase). Warnings-are-errors on the check log; make-check exit code honored; unknown variant fails closed. - _ci_privileged_single_test (root-only AutogroupNiceness case), _ci_coverage_python_wrapper, _ci_coverage_lcov helpers. - ci.bats: parser green/FAIL/zero + test unknown-variant.
Phase 1 (Refs #479): wire the content-based matrix and self-test. - ci_cmd_matrix: emit the build matrix JSON (variant x os) from the SOT, excluding opt-in variants (sanitizer). - ci_cmd_plan: write phases/build/matrix to $GITHUB_OUTPUT for the base..head diff, so the orchestrator needs no inline logic. - ci_cmd_selftest: run ci.bats in parallel (--jobs max(16,nproc*2)). - New impact class ci-engine (.github/scripts/**) -> selftest+lint, so a change to ci.sh/ci.bats runs the regression suite instead of NOOP. - impact/plan now cd into the repo root (consistent with build/test) rather than git -C. - ci.bats: matrix contents (default on both OSes, no sanitizer).
Phase 1 (Refs #479): first thin orchestrator workflow. Runs on pull_request/push and executes the ci.bats regression suite via `ci.sh selftest`, so the engine is verified in real CI. Runs alongside the existing legacy CI (which still provides the required checks); no legacy workflow removed yet. Orchestrator-clean (AG-CI-023): every run: is a single command; checkout pinned to a full 40-hex SHA. Passes the orchestrator/full-SHA/ line-endings guards.
The run-block scanner only matched `run:` at line start, missing the `- run: |` list-item form, so inline logic in that form went undetected (caught by ci.bats test 27 failing in real CI). Match an optional `- ` prefix. Real-tree violations rise 285 -> 288 as the missed forms are now seen.
The build matrix job needs each variant's dependency list. Add apt (ubuntu) and brew (macOS default) to build_matrix.variants in the SOT, folded from c-build.yml's per-job install-build-deps/brew inputs, and have ci_cmd_matrix emit apt for linux entries and brew for macOS entries. One owner for the dependency lists; the workflow reads them.
The plan job runs ci.sh plan (content-based impact) and outputs the build flag + matrix; build_test consumes the matrix (fail-fast off), installs per-variant deps (install-build-deps apt on Linux, brew on macOS), and runs ci.sh build/test per variant. ci_cmd_plan now falls back to a full run when the base commit is unknown (branch creation / workflow_dispatch). workflow_dispatch added so the matrix can be exercised without a c-source change.
- ci_cmd_plan used ${1:?...} which aborts on an empty base (the
workflow_dispatch case), before the unknown-base fallback could run;
accept an empty base and route it into the full-run fallback.
- The matrix self-test still expected the pre-apt/brew entry shape;
loosen it to match the current per-entry format.
_ci_privileged_single_test ran the AutogroupNicenessPrivilegeDrop_Case rerun on macOS too, where autogroups are a Linux-only kernel feature, so it reported NOTRUN and the guard failed the default/macOS build_test job (make check itself was clean: OK=171 NOTRUN=43 FAILED=0). Guard the rerun to Linux, matching c-build.yml's matrix.os == 'ubuntu-latest'.
actionlint/shellcheck flagged $BREW in 'brew install $BREW'; the word-split is intentional (a package list from the SOT). Add a scoped shellcheck disable directive as the first line of the run script.
ci_cmd_e2e wraps the existing 2-container distributed-compile harness (test/e2e/run-e2e.sh); the distributed-e2e-test composite action folds away. validate.yml gains an e2e job gated on the plan phases output.
Folds check-pr-title-convention.sh into ci.sh as _ci_check_pr_title (rule-71 taxonomy: allowed types/scopes, dependabot exemption, draft/warn/block modes). ci_cmd_metadata dispatches the PR-context checks; tracking-metadata and changelog gate follow. Part of replacing changelog-check.yml (removes the #479-SectionA cancel-in-progress race).
Folds check-pr-tracking-metadata.sh's enforced core (labels + milestone, rule 3; project-board full check best-effort as in the legacy degraded mode) and require_changelog (CHANGELOG.md touched or no-changelog-needed label). ci_cmd_metadata all runs title+tracking+changelog -- the replacement for changelog-check.yml's three PR-context checks.
- ci_cmd_package folds build-release-packages.sh (autogen, configure --enable-Werror, make deb) with a fail-closed missing-tool check. - _ci_check_release_version folds check-release-version.sh (configure.ac AC_INIT must equal the tag; tag must not already exist). ci_cmd_release version-check runs it; the actual tag/publish is a maintainer-gated outward action, not folded as an autonomous step.
Consolidated security scans as a workflow_dispatch-only cutover-draft (runs only when the cutover flips triggers and removes the legacy scan workflows; running both would double-upload SARIF). CodeQL c-cpp builds via ci.sh build default; ci_cmd_build now makes ccache optional so the no-ccache CodeQL apt set still builds. Pins folded verbatim from the legacy scan workflows. Full verification happens at cutover.
ci_cmd_container (nightly: docker build+push with SOT base ARG), ci_cmd_publish/_ci_publish_nightly (force-move the floating nightly tag with a v* guard, (re)publish the prerelease with built assets), and ci_cmd_e2e full (bidirectional matrix). nightly.yml is a workflow_dispatch-only cutover-draft (build/test/e2e via ci.sh + package + container + publish); the schedule trigger + legacy removal come at cutover. Fail-closed paths (unknown variant, v* tag guard) covered.
ci_cmd_gc is a verbatim fold of ghcr-cleanup.sh (untagged + old manual-N GHCR versions pruned, live multi-arch children protected, real/latest/ nightly tags never touched; DRY_RUN=true default). housekeeping.yml is a workflow_dispatch-only cutover-draft wiring it with package/dry_run inputs. Scheduled heartbeat + OpenSSF-recheck fold at cutover.
ci_cmd_container now builds plain/pump per platform (runtime/runtime-pump targets, SOT base ARG) and records the pushed digest; ci_cmd_publish gains manifest (buildx imagetools create from per-platform digests, amd64 required/arm64 best-effort, latest on real tag) and github-release (version-check gated + gh release with built assets). release.yml is a workflow_dispatch-only DRAFT (setup/build/test/e2e/package/container- matrix/manifest/github-release). DRAFT: Trivy/SBOM and run_attempt digest keying fold at cutover; needs real-tag verification before it goes live.
- Unknown commands, variants, modes and subcommands: seven tests become one. The real entry keeps one row (bash ci.sh bogus-command); the other ten rows call ci_main in-process with docker, make and configure forbidden, so "before any work" holds for every row. - sot set absorbs its rename/mode test; the duplicate missing-path case goes. - PR titles: one table on a fixture AG-GH-14 rule covers valid, no type, unknown type, unknown scope and empty titles in block, warn and draft (warn, draft, type and scope were untested before; mutants dropping the draft exemption or the scope check now fail). The taxonomy test parses a fixture rule instead of copying the real lists from AGENTS.md.
- Tracking: one table (both set, no milestone, whitespace-only label); the label branch was untested before and its mutant now fails. - Board check: one table (no PAT warns; on, off, lookup error with a PAT). - Release version-check: one fixture tag repo for all rows instead of one per test; the configure.ac mismatch row moves off the real repo. - Release context: one table per event (tag push, dispatch with and without opt-in, no tag, branch push, schedule). - Changelog event: one table (release and notes insert; pre-release and empty notes skip without an insert). - Registry: login and push without a token, then the stdin login, in one test. Mutants: dropping the label check, the pre-release skip or the existing-tag check each fail their table row.
- e2e compile-ok counter and server warning scan: each red case joins its green test. - changelog check: the opt-out label, zero-SHA and failed-diff rows join the merge-base test. - comfychair: one table for OK+NOTRUN, FAIL, no result line and a missing log (the last moved out of the mixed changelog/comfychair test); a mutant without the log check fails its row.
impact-hit, matrix, make gate/configure, report, output writer, artifact offer, cache plan, CFL run, draft release, OpenSSF checks and verdicts, OpenSSF recheck, gc protection and the run gate each keep one test; the red or second case runs in the same test. Shared state is reset between the parts (output files, the CFL workspace), the impact-hit broken-diff row runs before the path stubs it needs to bypass, and the OpenSSF advisory file no longer reuses a directory name.
Survivor pass with the bidirectional fault-separation rule: two tests stay separate only if one realistic defect fails A but not B and another fails B but not A. Tests that shared one final owner and one observable contract now run as one test each: impact path classes (5), plan (3), classify (3), glob (3), image build (3), container run (3), tool fetch (3), harden stop (3), tracking (2), changelog update (2), add-to-project (2), route (2), wait-until (2), download (2), sot refresh (2), SARIF upload (2), sot-update (2), harden start (2), and per lint guard: line endings, full SHA, unreadable or empty input, pins, comments, orchestrator, shellcheck directives. Parts that need the real function run before the part that stubs it (changelog retry, plan dispatch refs), and each part resets the files it asserts on (argv, tries, output, CI_MANIFEST, the git stub).
pr category (3 tests, one owner), tool fetch and its stuck partial dir, harden stop and its unreadable state, the OSV PR gate (2), the OpenSSF checks and the BR-01 scope test, and apt install with the image full-upgrade test each become one test. The harden stop parts reset the agent dir between them.
Every _ci_sot_* read ran awk over the whole build-manifest.yml; one ci.sh plan read the file 40 times. ci_require_manifest now indexes the SOT once in the parent shell (one awk pass, a C-locale sort, one eval of two arrays), so every subshell inherits the index, and a lookup is a binary search in bash with no fork. - Same rules as the old walker: first hit per path, children only from a section's first occurrence, the same value and comment parsing. 420 reads (every call site expanded against the real SOT, plus error controls) are byte-identical old vs new under bash 5 and bash 3.2 (busybox awk). - Validity: the index is keyed by the CI_MANIFEST path and dropped by _ci_sot_set; a caller that writes in a subshell drops its own index (ci_cmd_sot_update after _ci_sot_refresh). - New fail-closed ids: SOT-0011 odd indentation (the walker read it silently wrong), SOT-0012 unreadable SOT, SOT-0013 set on a section. The writer keeps its own awk rewrite (_ci_sot_write). - Values with ', $ and backticks round-trip exactly (reader table row); a mutant without the quote escaping fails loudly in eval, a mutant without the drop in _ci_sot_set fails the sot set test. - ci.bats indexes the real SOT once per file (setup_file) and each fixture once (_fixture_manifest): one shell variable per path made bats 40 ms slower per test, two arrays do not. Process counts (shimmed, buildtools image): ci.sh plan awk 56 -> 18 (SOT reads 40 -> 1); ci.bats awk 1305 -> 583 (SOT reads 896 -> 91).
- _ci_sot_list split each list with tr | sed | awk; it now splits in bash with the same rule (trim blanks, then one quote, at each end; drop empty items). - ci_cmd_matrix and _ci_release_matrix ran one jq per variant x os or variant x platform, then one more to join; one jq now builds each matrix from the collected rows. Output is byte-identical old vs new: 420 SOT reads under bash 5 and bash 3.2, and both matrices on the real SOT and on a fixture with backslashes, quotes, spaces and a bad optional flag. Process counts for ci.sh plan (dispatch), 9541430 -> this commit: awk 18 -> 3, jq 13 -> 4, sed 15 -> 0. ci.bats: awk 583 -> 284, sed 568 -> 269, jq 258 -> 233.
_ci_check_pr_title read AGENTS.md twice (types, scopes) and ran a grep|tr|tr pipeline per list plus two sed trims. _ci_title_rule now reads the rule line once and _ci_title_taxonomy parses a given line with a bash regex; the trims are parameter expansions. Called without a line, _ci_title_taxonomy still reads the rule itself. Per title check: AGENTS.md reads 2 -> 1, processes 9 -> 1 (one grep). Taxonomy lists, error ids and every title verdict (valid, trailing blanks, spaced subject, unknown scope/type, no subject, breaking, untyped, empty; block and warn) are identical old vs new on the real AGENTS.md.
label-pr (PR number, changed_files), the dispatch release context (tag, publish opt-in) and the release changelog event (prerelease, tag_name) ran one jq per field over the same event file. Each now reads its fields in one _ci_event_value call with a comma filter, the form _ci_event_range already uses. The multi-line release body stays a separate read. One stricter case: a release event with prerelease true but no tag_name used to be skipped without reading the tag; it now fails with EVENT-0001 like every other malformed payload. A GitHub release event always carries tag_name. jq per operation: label-pr 2 -> 1, release context 2 -> 1, changelog release 3 -> 2; ci.bats jq 233 -> 224. Lint 0, bats 119/119.
AGENTS.md [AG-INT-003] forbids hiding real output. ci.sh discarded command output at 24 places (docker rm/create/run ids, ccache, jq -e, command -v, SOT existence reads, sudo tee) and two command -v calls also discarded stderr; git rev-parse --quiet hid its error; the ssh self-test sent its host key to /dev/null. - Each discard now goes to stderr, where it stays visible in the log without polluting a function's stdout return value. - git rev-parse --verify drops --quiet; the ssh self-test keeps known_hosts in its own temp dir. - ci_main and the dispatch test read declare -F as a value instead of printing a function name on every dispatch. - ci.bats separates stderr (run --separate-stderr, bats >= 1.5) where a test compares a function's exact stdout, and its sudo stub writes to a file instead of /dev/null. Recurrence: ci_guard_shellcheck_directives becomes ci_guard_banned_texts (same ids) and also applies the new SOT list ci_engine.banned_ci_texts (/dev/null) to CI code in the CI-owned paths: scripts, workflows and Dockerfiles (CI-ERROR-GUARD-SHELLCHECK- 0006). The SOT itself is data and holds the banned texts. Product shell files are not in this list's scope. Mutants: dropping the CI-text check or its owned-path scope each fail the guard test. Lint 0, bats 119/119.
Update:
|
| Commit | Change | Validate / Security |
|---|---|---|
7673109, 3307fa2, 4e942a9 |
ci.bats tables: job count, SOT readers, unknown input, sot set, PR titles; three tests with no unique claim deleted | cancelled by the next push; covered by 9c58d7c |
9c58d7c |
tables for tracking, board, version-check, release context, changelog event, registry | success 37735127246 / 37735127212 |
673eca3, f86d58b |
red cases absorbed into their green tests (18 groups) | success 37736245367 / 37736245294 |
588b62c, 986f5b0 |
one test per owner where survivors shared one owner and contract (31 merges) | success 37737339277 / 37737339394 |
9541430 |
the SOT is read once per process (index + binary search) instead of one awk per lookup | success 37742411550 / 37742411572 |
826c438 |
SOT lists and both matrices without a process per item | success 37743490723 / 37743490584 |
55bcfb7, 236d65c |
the AG-GH-14 rule read once per title check; one-line event fields read in one jq run | success 37744282002 / 37744282027 |
71cb071 |
no output discarded to /dev/null in CI code, with a guard against recurrence |
success 37746075952 / 37746075935 |
Pushing every step cancelled the previous run (validate/security concurrency); from 9c58d7c on, the next push waited for the previous run to finish.
ci.bats: 218 -> 119 tests
The survival pass now applies this separation rule: two tests stay separate only with a concrete defect that fails one and not the other, in both directions; tests sharing one final owner and observable contract are rows of one test. 99 declarations were merged or deleted; every merged group kept all its assertions, and each table row names its case on failure. Mutants were run on the changed owners (job-count floor, SOT node kinds, unknown-input rejection, title draft/scope branches, label check, pre-release skip, existing-tag check, comfychair log check, tarball filter); each fails its row. The pass continues: separation-by-table for survivors with distinct owners but one call path, the checkout phase (no test yet), and the split of the popt test.
SOT read path (P1, 9541430)
Every _ci_sot_* read ran awk over the whole build-manifest.yml; one ci.sh plan read it 40 times. ci_require_manifest now indexes the SOT once in the parent shell (one awk pass, a C-locale sort, one eval of two arrays); every subshell inherits the index and a lookup is a binary search in bash.
- Equivalence: 420 reads (all 136 call sites expanded through their callers against the real SOT, plus error controls) byte-identical old vs new under bash 5 and bash 3.2; the macOS leg of
9541430built and tested through the new index (OK=171 NOTRUN=43 FAILED=0). - Validity: keyed by the
CI_MANIFESTpath, dropped by_ci_sot_set; a caller that writes in a subshell drops its own index. - New fail-closed ids:
CI-ERROR-SOT-0011odd indentation (the old walker read it silently wrong),CI-ERROR-SOT-0012unreadable SOT,CI-ERROR-SOT-0013set on a section. - Two rejected designs, measured: a pattern search on one string (
plan165 -> 3746 ms) and one shell variable per path (bats +38 ms per test).
Process counts for ci.sh plan (dispatch), before 9541430 -> after 236d65c: awk 56 -> 3, jq 13 -> 4, sed 15 -> 0. ci.bats: awk 1305 -> 284, SOT reads 896 -> 91. Real-CI ci.sh selftest step: 11 s at 986f5b0, 10 s at 9541430.
No output masking (71cb071)
AGENTS.md [AG-INT-003] forbids hiding output. ci.sh discarded output at 24 places (docker ids, ccache, jq -e, command -v, SOT existence reads, sudo tee), two of them also stderr; git rev-parse --quiet hid its error; the ssh self-test sent its host key to /dev/null. Each discard now goes to stderr; predicates read their value. ci.bats separates stderr (run --separate-stderr) where a test compares a function's exact stdout.
Recurrence: ci_guard_shellcheck_directives became ci_guard_banned_texts (same ids) and also applies the new SOT list ci_engine.banned_ci_texts (/dev/null) to CI code in the CI-owned paths: scripts, workflows and Dockerfiles (CI-ERROR-GUARD-SHELLCHECK-0006). Product shell files (autogen.sh, Makefile.in, pump.in) are outside this list's scope; that is an open maintainer decision.
Decisions needed
/dev/nullin product shell files (autogen.sh,Makefile.in,pump.in): separate issue/PR to remove it and extend the ban repo-wide, or keep upstream behaviour there.
[AG-CODE-004]
Each search ran on the parent of the introducing commit, over every CI file tracked there (git ls-tree of .github, docker, .clusterfuzzlite, test/e2e: 14 files).
- I confirm I did not violate [AG-CODE-004] -- I searched
9541430~1for an SOT cache or index (_CI_SOT,index,eval,declare -A) and found none, only the per-lookup awk walker_ci_sot_lookupwith its built-in set mode, so I am implementing_ci_sot_indexand_ci_sot_index_dropand splitting the set mode into_ci_sot_writein.github/scripts/ci.sh. - I confirm I did not violate [AG-CODE-004] -- I searched
9541430~1for a once-per-file bats setup (setup_file,BATS_FILE_TMPDIR) and found none, so I am implementingsetup_filein.github/scripts/ci.batsto index the read-only SOT once per file. - I confirm I did not violate [AG-CODE-004] -- I searched
826c438~1for list splitting in bash and found theread -raform in seven places of.github/scripts/ci.sh, so I am modifying_ci_sot_listto use that form instead oftr | sed | awk. - I confirm I did not violate [AG-CODE-004] -- I searched
826c438~1for matrix JSON builders and found onejq -cnper row plus a join inci_cmd_matrixand_ci_release_matrix, so I am modifying both in.github/scripts/ci.shto build each matrix in one jq run. - I confirm I did not violate [AG-CODE-004] -- I searched
55bcfb7~1for readers of the AG-Unbounded strcat command-line construction overflows 261-byte buffer in dcc_execvp_cyg (Cygwin) #14 rule and found only_ci_title_taxonomy, called once per list, so I am implementing_ci_title_ruleand modifying_ci_title_taxonomyand_ci_check_pr_titlein.github/scripts/ci.shto read the rule once. - I confirm I did not violate [AG-CODE-004] -- I searched
236d65c~1for multi-field event reads and found the comma-filter form in_ci_event_range, so I am modifying_ci_variables_label_pr,_ci_release_contextand_ci_changelog_from_eventin.github/scripts/ci.shto use that form. - I confirm I did not violate [AG-CODE-004] -- I searched
71cb071~1for banned-text checks (banned_shell_texts,_ci_banned) and foundci_guard_shellcheck_directiveswith_ci_banned_shell_texts, so I am extending that guard asci_guard_banned_textswith_ci_banned_textsand_ci_banned_hitsin.github/scripts/ci.shand the SOT listci_engine.banned_ci_textsin.github/yaml/build-manifest.yml.
Correction to #544 (comment): its parent searches ran over a hard-coded file list that named a Dockerfile that never existed and left out ci.bats and test/e2e. Rerun over the tree-derived list, one bullet changes: at afe08f9~1 the name:tag@sha256 form was also checked by a regex in a ci.bats test, besides _ci_sot_refresh. afe08f9 replaced that test with the ci_guard_sot_pins test, and 7b6294d made the two remaining ci.sh rules share _ci_pin_tracked. The other bullets are unchanged.
Updated 2026-10-08 12:37 CEST: two statements above are no longer current, see #544 (comment). Tests merged by appending bodies named their failing part only from 5cefa29 on; before, such a failure showed a bare [ ... ] line. The checkout phase test landed in 02f2042, and the popt test was rewritten in place in 3c41a3f instead of being split.
ci_cmd_checkout had no test; real CI runs only its green path. One table against a file:// fixture server: depth 1 is shallow at the requested commit, depth 0 holds the full history, an unknown ref fails without a checkout, and a missing GITHUB_REPOSITORY stops the command. A mutant that ignores the depth fails the shallow row. 120 tests; every CI_COMMANDS phase is now reached by a test.
Validate 37747652756 failed on 02f2042: the GitHub runner always sets GITHUB_REPOSITORY, the test inherited it, so the "missing env" row fetched instead of stopping (fatal: Needed a single revision). The evidence host had no such variable, so the row passed there. Reproduced with GITHUB_REPOSITORY set; the row now removes it with env -u, as the other tests do with the runner variables they need absent. The other env-dependent tests already unset theirs.
The appended-body merges (f86d58b, 588b62c, 986f5b0) kept all assertions, but a failure showed only a bare [ ... ] line. Each part now starts with echo "case: <original test name>", so the last case line in a failing test's output names the failing part. Placement was derived from the pre-merge files and checked mechanically: all 107 markers start at a line of their original test or at a state reset. The rows added in 71cb071 to the banned-text test get their own case.
The strict-compile half of the popt test ran gcc stubbed to pass, so it proved the loop but not what the loop passes to gcc. gcc is now the existing _fake_tool on PATH: the test asserts exactly five calls, each with the exact flag run -Isrc -Ipopt -Wall -Wextra -Werror -Wno-unused -Wno-unused-parameter for its popt/*.c file, and that a failing gcc stops after one call with rc 1 and its error shown. Four mutants of _ci_popt_strict_compile are each killed at their own assertion: -Werror dropped, -Wno-error added, no stop on error, one file dropped. Whether the real popt sources build Werror-clean stays proven by the real popt-vendor CI leg.
Update:
|
| Commit | Change | Validate / Security |
|---|---|---|
02f2042 |
checkout phase test: shallow and full fetch through a file:// remote, a bad ref, missing env |
Validate failure 37747652756 (below) |
e0dbf15 |
the missing-env row of that test drops GITHUB_REPOSITORY |
success 37760205992 / 37760206078 |
5cefa29 |
each part of a merged test names its case | success 37761172395 / 37761172315 |
3c41a3f |
popt strict compile pins its flags, file set and stop | success 37763277138 / 37763277307 |
Red run on 02f2042
Validate 37747652756 failed in ci.sh selftest (and therefore gate): the GitHub runner always sets GITHUB_REPOSITORY, the missing-env row inherited it, and checkout fetched instead of stopping (fatal: Needed a single revision). The local runs had no such variable, so the row passed there. Reproduced with the variable set; e0dbf15 starts that row with env -u GITHUB_REPOSITORY. Local iteration runs of ci.bats now set the runner variables (CI, GITHUB_ACTIONS, GITHUB_REPOSITORY, GITHUB_SERVER_URL, GITHUB_SHA), so this class shows before CI.
ci.bats: 119 -> 120 tests
- checkout was the one CI entry point without a test. Reach is now 39/39 entry points (28 phases, 11 guards), counted from
CI_COMMANDSand the lint guards, with thebash ci.sh <cmd>entry mapped toci_mainand itsci_cmd_*. - Case markers (
5cefa29): in tests merged by appending bodies, each part starts withecho "case: <original test name>". bats 1.11.1 prints a test's own stdout only when the test fails (writing-tests.md, "Printing to the terminal"), so the last case line names the failing part. 107 markers; each was checked to start at a line of its original test or at a state reset. A mutant on the artifact offer's empty-set check fails with the marker of that part. - popt test (
3c41a3f): the strict-compile half rangccstubbed to pass, so it proved the loop, not what reaches gcc.gccis now_fake_toolonPATH; the test asserts exactly five calls, each with the exact flag run-Isrc -Ipopt -Wall -Wextra -Werror -Wno-unused -Wno-unused-parameterfor itspopt/*.cfile, and that a failing gcc stops after one call with rc 1 and its error shown. Four mutants of_ci_popt_strict_compilefail at their own assertion:-Werrordropped,-Wno-erroradded, no stop on error, one file dropped. Whether the real popt sources build Werror-clean stays proven by the realpopt-vendorleg. - Earlier mutants rerun on the merged suite (raw logs kept): the guard input check (no-op helper, dropped call), the pin form, the release asset hits and the tarball filter are still killed by the same tests as before the merges.
Corrections to #544 (comment)
- "each table row names its case on failure" did not hold for tests merged by appending bodies: a failure there showed only a bare
[ ... ]line until5cefa29added the case markers. - "The pass continues: ... the
checkoutphase (no test yet), and the split of the popt test": checkout is covered since02f2042. The popt test was rewritten in place, not split: both halves keep their own case marker and assertions, and no required property needs a separate test.
[AG-CODE-004]
Each search ran on the parent of the introducing commit, over the 14 CI files tracked there (git ls-tree of .github, docker, .clusterfuzzlite, test/e2e).
- I confirm I did not violate [AG-CODE-004] -- I searched
02f2042~1for a checkout test or a fixture remote (checkout,file://,clone --bare,_fixture_tag_repo) and found no test ofci_cmd_checkout, only the tagged fixture repo_fixture_tag_repo, so I am implementing the checkout test in.github/scripts/ci.batson that fixture. - I confirm I did not violate [AG-CODE-004] -- I searched
e0dbf15~1for how tests drop a runner variable (env -u GITHUB_,unset GITHUB_) and foundunsetin three tests that call functions in the test shell; the checkout row already starts ci.sh throughenv, so I am modifying that row in.github/scripts/ci.batsto useenv -u GITHUB_REPOSITORY. - I confirm I did not violate [AG-CODE-004] -- I searched
5cefa29~1for a way a merged test names its failing part (echo "case:,BATS_TEST_DESCRIPTION,bats_test_function) and found none, so I am implementingecho "case: ..."lines in.github/scripts/ci.bats. - I confirm I did not violate [AG-CODE-004] -- I searched
3c41a3f~1for a stub that logs its arguments and takes an exit code (_fake_tool,_record,_fail,_stubbed,.args) and found_fake_tool, so I am modifying the popt test in.github/scripts/ci.batsto use it for gcc.
The lint entry test proved the wiring of one guard only (the banned text guard ran for real, six others were stubbed to pass). A second case now records all 13 lint steps: with none failing, the call set must equal the exact wiring (line endings per owned path, the full-SHA scan on .github/workflows, .github/yaml and docker, root guards on the root, workflow guards on the workflow files, error ids on ci.sh, both linters); then each step fails once, lint must return 1, and every other step must still have run. Five wiring mutants are each killed at their own assertion: a dropped guard call, a swallowed failure in the middle and at the end, a stop after the first failure, and a full-SHA dir dropped.
…ailed step The release image test grepped two of the six build calls (install, pump rename) and the runtime useradd, and checked the stop only for a failing make and a failing apt install. It now asserts the exact ordered calls of both image steps: the SOT apt packages in image mode, configure with --enable-Werror and --without-system-popt (the vendored popt carries the CVE fixes), the gated make, the install into /out, make install into /out-pump, the pump rename, and the nologin system user. Each step then fails once: the function returns 1 and no later step ran; a failing nproc stops with 2 before make. Five mutants are each killed at their own assertion: configure not stopping, system popt, apt runner mode, a swallowed make install failure, a login shell for the distcc user.
…nd ref The plan test compared ci_cmd_plan's phases with _ci_all_phases, the function plan itself calls, so a defect there changed both sides. A fixture SOT with three impact classes now gives the literal phase set build package verify. One table covers dispatch and schedule on current_dev, a dispatch on master (not tested before), bot/x and 544/merge; each row checks phases, build, matrix and publish_buildtools together. A new case checks that a SOT without the verify and build phases never publishes buildtools and never builds a matrix. Six mutants are each killed at their own row: master not protected, every ref protected, publish without verify, build without a matrix, a matrix without build, an empty phase set.
Update:
|
| Commit | Change | Validate / Security |
|---|---|---|
f643e31 |
lint runs every guard on its input and fails if any fails | success 37764800695 / 37764800934 |
5e1cd37 |
release images use the SOT packages and stop at the first failed step | pushed together with 7db73ae, covered by its run |
7db73ae |
plan expects literal phases from a fixture SOT, per event and ref | success 37766771070 / 37766770988 |
ci.bats (120 tests, three rewritten)
- Lint wiring (
f643e31): the lint entry test proved one guard end to end. A second case records all 13 lint steps: with none failing, the call set must equal the exact wiring (line endings per owned path; the full-SHA scan on.github/workflows,.github/yamlanddocker; root guards on the root; workflow guards on the workflow files; error ids on ci.sh; actionlint and shellcheck). Then each step fails once: lint returns 1 and every other step still ran. Five mutants ofci_cmd_lintare each killed at their own assertion: a dropped guard call, a swallowed failure in the middle and at the end, a stop after the first failure, a full-SHA dir dropped. - Release images (
5e1cd37): the test grepped two of the six build calls and the runtimeuseradd, and checked the stop only for a failing make and apt install. It now asserts the exact ordered calls of both image steps (SOT apt packages in image mode, configure with--enable-Werror --without-system-popt, gated make, install into/out,make installinto/out-pump, the pump rename, the nologin system user), and each step fails once: rc 1 and no later step ran; a failing nproc stops with rc 2 before make. Five mutants are each killed at their own assertion: configure not stopping, system popt, apt runner mode, a swallowedmake installfailure, a login shell for thedistccuser. - Plan (
7db73ae): the test comparedci_cmd_plan's phases with_ci_all_phases, whichci_cmd_planitself calls, so a defect there changed both sides. A fixture SOT with three impact classes now gives the literal setbuild package verify. One table covers dispatch and schedule oncurrent_dev, a dispatch onmaster(not tested before),bot/xand544/merge, each row checking phases, build, matrix andpublish_buildtoolstogether; a new case checks that a SOT withoutverifyandbuildnever publishes buildtools and never builds a matrix. Six mutants are each killed at their own row:masternot protected, every ref protected, publish withoutverify, build without a matrix, a matrix without build, an empty phase set. - Table loops: all 13 heredoc-driven table loops in ci.bats run every row (iterations counted against rows in an instrumented copy), so no loop body consumes its table from stdin.
Correction to the f643e31 commit message
It says the lint test had "six others" stubbed. Before f643e31 the test stubbed nine steps (ci_guard_line_endings, ci_guard_full_sha, ci_guard_pins_in_sot, ci_guard_sot_mirrors, ci_guard_orchestrator_only, ci_guard_job_timeouts, ci_guard_comment_format, _ci_lint_actionlint, _ci_lint_shellcheck); ci_guard_banned_texts, ci_guard_sot_pins, ci_guard_path_mirrors and ci_guard_error_ids ran for real. The pushed message is not rewritten.
[AG-CODE-004]
Each search ran on the parent of the introducing commit, over the 14 CI files tracked there.
- I confirm I did not violate [AG-CODE-004] -- I searched
f643e31~1for a way to record and fail single steps and for existing lint tests (_record,_fail,ci_cmd_lint,read -r guard) and found the_recordand_failstubs and the one-guard lint test, so I am modifying that test in.github/scripts/ci.batsto record every lint step with_recordand fail each with_fail. - I confirm I did not violate [AG-CODE-004] -- I searched
5e1cd37~1for call-order assertions on the release image steps (_record,cat "${CALL_LOG}",_ci_image_release_build,_ci_image_release_runtime) and found the_recordstub and the grep-based release image test, so I am modifying that test in.github/scripts/ci.batsto compare the full recorded call list. - I confirm I did not violate [AG-CODE-004] -- I searched
7db73ae~1for fixture SOTs and plan tests (_fixture_manifest,_ci_all_phases,ci_cmd_plan) and found_fixture_manifestand the plan test that derived its phases from_ci_all_phases, so I am modifying that test in.github/scripts/ci.batsto use a_fixture_manifestSOT with a literal expectation.
…ixture The impact test expected exact phase lists per path class from the real SOT, a second copy of the SOT's routing: any routing edit broke it without a defect. The real SOT now gets contract rows only: a docs-only diff is NOOP, a C source runs build, and a SOT or ci.sh change selects exactly the phases validate.yml gates on (read from its contains(needs.plan.outputs.phases, ...) gates and the build gate). The mechanics run on a fixture SOT: the sorted, de-duplicated union over matched classes, a path in two classes, NOOP for unmatched or phase-less classes; a class without a phases list and a failing classifier are rc 2, never NOOP. Five mutants are each killed: no sort or dedupe, no NOOP, a swallowed classifier error, a swallowed SOT error, only the first class used.
…alone ci_guard_sot_mirrors skipped its housekeeping.yml and dependabot.yml checks silently when the file was absent, while its validate.yml and CFL Dockerfile checks logged NotRun. Both now log "NotRun: <file> absent" in the same form. No other existence check in ci.sh skips silently. The mirror test ran the guard on the real tree, which the lint job already does, then planted every drift at once and only looked for each id somewhere in the output. It now starts from a clean fixture tree that must pass without any CI-ERROR, applies one drift per row, and requires exactly that row's id (cron, package option, milestone changed or missing, task option, unwired phase, CFL FROM), plus a NotRun row for the absent files. Six mutants are each killed: either NotRun line dropped, the 0002, 0009 or 0010 failure swallowed, the milestone check raising the wrong id.
…reutils Ten assertions inside table loops failed with a bare [ ... ] line, so the failing row was not named (live PR fetch, metadata events, self-compile passes, popt CVE ids, timeout guard args, attest claims); the CI_COMMANDS test did not print the missing names. Each now names its row. The verify-all / brew test gets a case line per part, and a printf in the SOT reader test had a literal newline in its format. The attest claims test meant to prove that _ci_attest_claims pads the JWT payload before base64 -d. coreutils 9.5 stopped requiring padding, and the buildtools image has 9.7, so removing the padding still passed. The attest step runs on ubuntu-24.04 runners with coreutils 9.4, which rejects unpadded input. The test now keeps what base64 -d receives and requires a length divisible by 4, and two new claims encode to / and + so the base64url alphabet swap is covered too. Both mutants (no padding, no swap) are killed.
Update:
|
| Commit | Change | Validate / Security |
|---|---|---|
47800e5 |
impact checks real-SOT contracts and the phase union on a fixture | success 37768629411 / 37768629532 |
6a8a480 |
mirror guard reports absent mirrored files; test each drift alone | pushed together with 4aea428, covered by its run |
4aea428 |
table rows name themselves; attest proves padding on any coreutils | success 37770739362 / 37770739351 |
Changes
- Impact (
47800e5): the test expected exact phase lists per path class from the real SOT, a second copy of the SOT's routing. Real-SOT rows are now contracts only: a docs-only diff is NOOP, a C source runs build, and a SOT or ci.sh change selects exactly the phasesvalidate.ymlgates on (read from itscontains(needs.plan.outputs.phases, ...)gates and the build gate). The mechanics run on a fixture SOT (sorted, de-duplicated union over matched classes; a path in two classes; NOOP for unmatched or phase-less classes); a class without a phases list and a failing classifier are rc 2, never NOOP. Five mutants are each killed. - Mirror guard (
6a8a480, ci.sh): an absenthousekeeping.ymlordependabot.ymlskipped its check without output, while the guard's other two mirrored files logged NotRun. Both now logNotRun: <file> absent. All 23 existence checks in ci.sh were read in context; no other silent skip. The test replaces its real-tree run (the lint job already runs the guard on the real tree) with a clean fixture tree that must pass without any CI-ERROR, then applies one drift per row and requires exactly that row's id; a NotRun row covers the absent files. Six mutants are each killed. - Row labels (
4aea428): ten assertions inside table loops failed with a bare[ ... ]line and did not name the failing row; each now does. The verify-all / brew test gets a case line per part. - Attest padding (
4aea428): the attest claims test was meant to prove that_ci_attest_claimspads the JWT payload beforebase64 -d. coreutils 9.5 (NEWS, 2024-03-28: "base32 and base64 no longer require padding when decoding") made that invisible in the buildtools image (coreutils 9.7): removing the padding still passed. The attest step runs on ubuntu-24.04 runners (e.g. job 113282442227, image 20261004.327.1), and Ubuntu 24.04 ships coreutils 9.4, which rejects unpadded input; so the padding is needed and was untested. The test now checks thatbase64 -dreceives a length divisible by 4, and two new claims encode to/and+, covering the base64url alphabet swap. Both mutants (no padding, no swap) are killed.
ci.bats run time, measured
Same host, same buildtools image plus bats 1.11.1, three alternating runs of 7b6294d (218 tests) and the 4aea428 content (120 tests):
7b6294d |
4aea428 |
median change | |
|---|---|---|---|
parallel (--jobs 16) |
6041 / 6017 / 6063 ms | 4118 / 4145 / 4400 ms | -31% |
| serial | 18227 / 18496 / 18242 ms | 14167 / 13745 / 13680 ms | -25% |
| sum of per-test times | 11277 / 11450 / 11343 ms | 10584 / 10275 / 10109 ms | -9% |
Most of the serial gain is per-test setup that 98 fewer declarations no longer pay. The merged tables are now the slowest tests (impact 614 ms, lint wiring 503 ms, mirror drift 419 ms, medians); the plan test dropped from 501 + 444 ms (two tests on the real SOT) to 304 ms.
Test image vs runner tools
The attest gap came from a tool that behaves differently in the test image than on the runner, so the CI tools were compared: buildtools has coreutils 9.7, mawk 1.3.4 20250131, git 2.47.3, curl 8.14.1; the ubuntu-24.04 runner has coreutils 9.4, mawk 1.3.4 20240123, git 2.55.0, curl 8.5.0 (sed 4.9 and jq 1.7 on both). The coreutils 9.5 to 9.7 behavior changes and the mawk changes in between were read in full against ci.sh: only the base64 padding change touches CI code. git, bash patch level and curl differences were not swept; curl and gh are stubbed in the tests, and the git paths run in real CI on every pull request.
Ledger state
All 220 rows of the ci.bats ledger resolve to a current test (own test, case line, absorbed row or renamed) or to a recorded deletion (3); the 120 tests are those homes plus the two new tests without a ledger row (checkout, path mirror guard). Rows still waiting on a maintainer decision: the draft exemption of the tracking gate, and the rows on retry classification and the image full-upgrade, whose scope is an open question.
[AG-CODE-004]
Each search ran on the parent of the introducing commit, over the 14 CI files tracked there.
- I confirm I did not violate [AG-CODE-004] -- I searched
47800e5~1for fixture SOTs and readers of the validate.yml phase gates (_fixture_manifest,_ci_phases_for_paths,contains(needs.plan.outputs.phases) and found_fixture_manifestand_ci_unwired_phases, which reads the same gates in the other direction (SOT phase to job) for the mirror guard; the test needs the gates as an expectation independent of that guard, so I am modifying the impact test in.github/scripts/ci.batsto use_fixture_manifestand to read the gates from validate.yml itself. - I confirm I did not violate [AG-CODE-004] -- I searched
6a8a480~1for the NotRun form for an absent file (NotRun: ${f} absent,ci_guard_sot_mirrors) and found it in two branches ofci_guard_sot_mirrorsand two ofci_guard_path_mirrors, so I am modifyingci_guard_sot_mirrorsin.github/scripts/ci.shto use the same line in its two remaining branches. - I confirm I did not violate [AG-CODE-004] -- I searched
4aea428~1for a stub that keeps a command's stdin and still runs the real command (base64 -d,_ci_attest_claims,command base64, plus the_record/_fake_toolhelpers) and found none (_recordreplaces the command,_fake_toollogs only arguments), so I am implementing abase64wrapper inside the attest claims test in.github/scripts/ci.bats.
…nknown ci_cmd_e2e piped _ci_sot_optional into grep -q to test whether a mode exists. When the SOT read failed (e.g. the workload key holds a section, CI-ERROR-SOT-0010), pipefail made the test false and the mode was also reported as unknown (CI-ERROR-E2E-0013), a second, wrong cause. The value is now read first: a read error returns 2 with only the SOT id; an empty value is an unknown mode as before. This also removes the pipe into an early-exit reader. The unknown-command test gets a row for the unreadable entry. Two mutants are killed: dropping the new return, and the old pipe form.
47800e5 replaced the real-SOT rows for a C source (build e2e package), an include-server file (build e2e) and an unmatched path (NOOP) with "a C source includes build". That weakened the check: removing package from the c-source class in the SOT no longer failed any test. The three exact rows are back, next to the fixture mechanics and the validate.yml gate row. A SOT mutant without package in the c-source phases fails at the c-source row.
Maintainer direction (2026-10-08): impact is content-based, not path-basedRecorded from the maintainer's review of this PR on 2026-10-08.
Current state against this direction
|
80e192f restored real-SOT rows that hard-coded product paths (src/dopt.c, include_server/basics.py, LICENSE) and copied the SOT's phase lists. Issue #479 asks for fixtures, and copied SOT data only proves that two copies agree; the routing per path class is owned by the SOT itself. These rows are removed. The one routing contract with a binding source is that a docs-only diff selects nothing (DEFAULT=NOOP). The test now takes every tracked file that the SOT's labels.documentation map names (one git ls-files with the SOT globs as pathspecs, no path written in the test) and requires NOOP for that diff. A SOT mutant giving the docs class a build phase fails it.
DEFAULT=NOOP: a comment- or format-only edit must select no work, but ci.sh impact (and impact-hit) selected a class's phases for any edit to one of its paths. Each changed path is now compared at base and head after normalization by the real parser for its type: - shell and bats: bash parses the file as a function body and prints it with declare -f (comments dropped, heredocs and strings kept, nothing executed); the shebang is compared on its own; - C sources and headers: gcc -fpreprocessed strips comments; an added line with /*, */, ??/ or a trailing backslash still counts, since a comment edit like that can break a -Werror build (-Wcomment); - the SOT: the index of its own reader (_ci_sot_dump); - Dockerfiles: comment lines dropped, parser directives kept, a file with a heredoc always counts. Line endings are ignored. Any other file type, and any added, deleted or unparsable file, counts on every edit (unsure means changed). The OSV PR gate read the base SOT with its own ls-tree/show code and a literal SOT path; both now use the shared _ci_rev_file and _ci_sot_relpath. The impact test gets a 19-row table on a fixture repo; eight mutants of the filter are each killed at their own row.
Summary
Full rewrite of the distcc-ng CI per #479: one Bash engine, one regression suite, one machine-readable source of truth, and five thin orchestrator workflows. Draft while the remaining items below are open. The step-by-step record, with evidence per commit, is the living status comment: #544 (comment)
Refs #479
Structure
.github/scripts/ci.sh: the only CI engine. Fail-closedCI_COMMANDSdispatch, an SOT index built once per process (one awk pass, binary-search lookups), one owner per capability (image build, container run, stacks, downloads, registry, attestation, artifacts, release identity, event values)..github/scripts/ci.bats: the regression suite for every phase and guard, green and red paths via fixtures..github/yaml/build-manifest.yml: the sole pin and policy owner (image digests asname:tag@sha256, tool versions with mandatory sha256, the two transport-only action pins, build matrix with each variant'sconfigure,cflags,ldflags,ccache,check_env,build_stepsandtest_steps, impact classes, housekeeping tasks, release inventory)..github/workflows/{validate,security,release,nightly,housekeeping}.yml: orchestration only.run:calls oneci.shcommand; the onlyuses:steps areactions/cacheandactions/upload-artifact, pinned in the SOT and fed exclusively byci.shstep outputs.scripts/, all.github/actions/composite actions, the e2e orchestrator scripts,docker/verify/selftest-ptrace.shand.github/labeler.yml; their logic lives inci.sh, their data in the SOT.Guards (lint)
run:steps;uses:only as an exact SOT action pin withsteps.*.outputsinputs.ARG; an unused SOT action fails.What:/Why:/From:comment form (AGENTS.md[AG-CODE-001],From:holds Issue/PR pointers only), actionlint, shellcheck.name:tag@sha256:<64 hex>and a sha256 beside every toolurl(ci_guard_sot_pins).workflow_dispatchoptions (package,task), Dependabot milestones, the validate.yml job gate of every SOT phase, the ClusterFuzzLiteFROM(ci_guard_sot_mirrors); Dockerfile paths and SOT refs that repeatci.shconstants: the/cibind target, the release/outtrees, the CFL$SRCdirectory, the registry (ci_guard_path_mirrors).timeout-minuteson every workflow job (ci_guard_job_timeouts) and eachCI-ERRORid raised in one place (ci_guard_error_ids).shellcheck disable=orshellcheck source=/dev/nullin any shell source of the repository (AGENTS.md[AG-INT-003]: a silenced warning is itself a violation); the guard reads the banned texts from the SOT listci_engine.banned_shell_texts.shellcheck -xover every shell source of the CI-owned tree. No output discarded to/dev/nullin CI code (scripts, workflows, Dockerfiles of the CI-owned paths; SOT listci_engine.banned_ci_texts); both lists are checked byci_guard_banned_texts.Validation
ValidateandSecuritygreen on6d5bb78,dcbd4d2,0202a94,f046f5a,ed87de8,3376e30,f5cf0e0,66a9f8a,cd2af70,b072143,115bdb9and the current tip50f724a(the living comment lists every tip, run detail and the red runs with their root cause).6008af2:Validate37617710562 andSecurity37617710631 success. On8485e4f:Validate37635061911 andSecurity37635062363 success. On92fbc91:Validate37639401637 success; its e2edistributedmode ran the self-compile through the new warning gate (ng-ng-plain: 100 COMPILE_OK,ng-ng-pump: 98 COMPILE_OK, noBUILD-WARNline). On617bfc3:Validate37640296491 andSecurity37640296459 success. On00d18de:Validate37644342257 success,Security37644342303 failure (SARIF upload retry, fixed in9ca629a). On9ca629a:Validate37646260743 andSecurity37646260647 success. Onc5f36bc:Validate37649000208 andSecurity37649000249 success. On36be7f2:Validate37651263407 andSecurity37651263488 success. On9f5c935:Validate37658638162 andSecurity37658638053 success. On the tipce9f997:Validate37678404656 andSecurity37678404742 success (feat(ci): CI Rewrite 1.2 with one engine, one SOT and five thin workflows #544 (comment)).7673109to71cb071(218 -> 120 tests with the checkout test; process counts and equivalence evidence;[AG-CODE-004]confirmations): feat(ci): CI Rewrite 1.2 with one engine, one SOT and five thin workflows #544 (comment).150f42fto7b6294d(walker node kinds, SOT-owned variants and housekeeping tasks, path constants, guards moved from ci.bats intoci.sh lint, guard input ids, one owner each for the asset globs and the pin form,label-prover 300 files): per-commit CI, the5eb9389macOS regression and its fix in37cde7a, the walker check on paths PR CI does not run (412/412 reads on the real SOT) and the[AG-CODE-004]confirmations are in feat(ci): CI Rewrite 1.2 with one engine, one SOT and five thin workflows #544 (comment).60b0f95:Validate36979646233 andSecurity36979646260 success.643b8ec:Validate37004243842 andSecurity37004243750 success; the verify job (110828834639) logs[CI-SELFTEST] actionlint: OKwith the SOT-pinned release binary, and every image build stage logs0 not upgradedafterapt-get full-upgrade(validate run 37000169812).0ba323e: 112 ccache hits on ubuntu, 106 on macOS from the previous run's save);coverage-reportsandscorecard-resultsartifacts uploaded; build-provenance attestation stored and verified as SLSA v1 (68fd599).ghcr.io/wiki-mod/distcc-ng-buildtools@sha256:d9964924…on the evidence host:ci.sh lint(actionlint, shellcheck, every guard) green on the tree of each code commitd6f5d2ftoebf88c0before it was pushed, except680b203, red on the two Alpine initd suppression lines until7d37820(as its commit message records); for the docs-only commitscdfadd7and60b0f95the evidence is the real CI lint job (job 110751304473, success). For4527c34to643b8ecand92fbc91toce9f997the sameci.sh lintran green on each tree before it was pushed. That image has no bats, soci.bats(219/219 atce9f997) was iterated in a throwaway container of it plus aptbats, which is not AG-VAL-003 evidence; the evidence forci.batsis the real CIengine-selftestjob. Each new test was also run against the previousci.sh(or workflow) and failed there.Audit closure
The three audits of
50f724a(owner/coverage, return paths, short form) are closed per finding in #544 (comment): status, path and line at the tip, rule, fix commit, green test and red evidence for every ID, evidence limits stated as measured.Every file this PR deletes (57) is mapped to its new owner, or marked obsolete with the reason, and #479 is checked requirement by requirement in #544 (comment). That audit found and fixed the lost daily full-matrix check of
current_dev(1ef8c35), phases without fixture tests (cec4c35,9091aac,848dcc5),.trivyignore.yamloutside the comment guard (05c4885), and two apt diagnostics/bounds (cec4c35,ce9f997).Merge prerequisites (maintainer)
Rulesets.
current-dev-protect(20746300) anddistcc-ng-default(18300729) still require the legacy contexts (make_check (ubuntu-latest),make_check (macOS-latest),action-lint,require_changelog,shellcheck,scan-pr / osv-scan,PR title Conventional-Commit lint,PR tracking metadata (labels/milestone/project),Test coverage (gcov/lcov, job summary + artifact),Bundled popt fallback build (no system libpopt),Vendored popt/ version and compile check,Distributed compile E2E (2-container)). None of them is emitted after this PR, so every PR would wait on them forever. Both rulesets need, in lockstep:make_check (*),Bundled popt fallback build …,Vendored popt/ …,Distributed compile E2E (2-container),Test coverage …,action-lint,shellcheckValidate (required)(gate over lint, self-test, plan, build/test matrix, e2e, container, package, verify)require_changelog,PR title Conventional-Commit lint,PR tracking metadata (labels/milestone/project)PR metadata (title/tracking/changelog)Analyze (c-cpp),Analyze (python),Analyze (actions)scan-pr / osv-scanOSV scanRulesets are maintainer-exclusive (
AGENTS.md[AG-SEC-006]); this PR does not change them. CI Rewrite 1.2 #479 asks for the ruleset change to be dry-run first, for example both rulesets inevaluatemode with the new contexts before they are switched toactive.Resolved inAGENTS.md[AG-CI-001]versus CI Rewrite 1.2 #479's target structure.6008af2and8485e4fon the maintainer's ruling:[AG-CI-001]now requires CI logic once inci.sh, every pin in the SOT and oneci.shcommand perrun:step, and forbids composite actions under.github/actions/.Stale file references inResolved inAGENTS.md.8040030:[AG-GH-002]now names.github/workflows/validate.yml'smetadatajob, and[AG-GH-014]names.github/scripts/ci.sh's_ci_check_pr_title, which reads its types and scopes from that rule's own line (e7d7853). Only the file references changed, no requirement. Please confirm the wording when you review.GitHub Code Quality. CI Rewrite 1.2 #479's Constraints remove both GitHub-managed CodeQL entries (default code-scanning and code quality). Default code-scanning setup is
not-configured;CodeQL - Code Quality(dynamic/github-code-quality/codeql) still runs onmaster(last run 37297303872 on 2026-10-05, checked 2026-10-07). Turning it off is a repository setting. For information only: this PR'scodeqljob runssecurity-extended, so Code Quality's quality queries run nowhere else.Schedules, dispatches and Dependabot read
master's workflow copies only, so the new housekeeping/nightly schedules,sot-updateand.github/dependabot.ymltake effect with the release that promotes this tomaster.Open decisions from the 2026-10-02 audit pass.All settled:ci.sh image cfl-toolchainwrites CFL's$SRC/build.shand.clusterfuzzlite/build.shis gone, every job hastimeout-minutes,_ci_fetch_toolfails closed (7f5e2a3); the unreachable fork-PR branch of_ci_check_pr_boardis removed and actionlint is a SOT-pinned release binary with nogolangbuilder stage (643b8ec). (b)[AG-INT-006]is removed and its texts are the SOT list (6008af2); the duplicate e2e harness is gone sincec339651, oneci.sh e2eharness remains (feat(ci): CI Rewrite 1.2 with one engine, one SOT and five thin workflows #544 (comment)). Recorded as pre-existing and outside this rewrite: the trivy--ignore-unfixedand OSV pomxml flags (feat(ci): CI Rewrite 1.2 with one engine, one SOT and five thin workflows #544 (comment)).Milestone on bot pull requests.Resolved in9f5c935from[AG-GH-002]and the milestone the repository's own milestoned pull requests carry: bot pull requests carry the SOT milestonebot_milestone("current_dev backlog"),sot-updateadds its pull request to the board, anddependabot.yml's milestone is bound to the SOT.Open decisions from the 2026-10-08 audit pass (CodeQL
--download, popt-vendorpopt-strict, the release CHANGELOG push tocurrent_dev): feat(ci): CI Rewrite 1.2 with one engine, one SOT and five thin workflows #544 (comment). Also open:/dev/nullin the product shell filesautogen.sh,Makefile.inandpump.in(feat(ci): CI Rewrite 1.2 with one engine, one SOT and five thin workflows #544 (comment)).Not exercised by this PR's CI
report, the OpenSSF recheck,sot-update, nightly (including the harden-runner agent lifecycle and e2efull) and the release jobs run only on schedules, tag pushes or dispatches frommaster. The ClusterFuzzLite crash upload has never fired, because no fuzz run crashed. Thevariablesjob (label-pr,add-to-project) runs onpull_request_targetandissues, which use the default branch's workflow file, so it has not run in its new form either; that includes thelabel-prfix in7d62fba. ci.bats covers their decision paths.Upstream relevance (
AGENTS.md[AG-UP-001])Everything else here is this fork's own CI. Non-CI fixes touch
packaging/RedHat/rpm.specand the Alpine OpenRC units:655908b: a prose comment that made rpm expand a second setup macro came from this fork's own commit 9990404; upstreamdistcc/distcc'spackaging/RedHat/rpm.spechas no such comment (checked 2026-10-01), so no entry applies.97710efand00335ea: two commented-out directives that rpmbuild still expanded ("Macro expanded in comment"), the unknown configure option--with-docdirand the absolute masquerade symlinks all come from upstream's own spec at8d569d19(checked 2026-10-01); the comment macros are also visible in make deb fails distcc/distcc#418's log.8783920and16f32a5addsupport-upstream/issue-479-rpm-spec-build-warnings.mdand its index row 60.6008af2to617bfc3change only this fork's CI engine, suite, SOT and governance text, so no upstream entry applies. The same holds for150f42fto7b6294d(ci.sh, ci.bats, the SOT, release.yml, the release Dockerfile comment and the CI docs).7d37820: the twopackaging/Alpine*/distccd.initdOpenRC units are this fork's own; upstreamdistcc/distcchas nopackaging/Alpine(checked at8d569d19, 2026-10-02), so no entry applies.Review state
0 unresolved review threads (checked 2026-10-08). Self-audit and VER-* classification for
617bfc3are in the living comment; real CI of the tipce9f997and its self-audit (34 files; functions 338/338, tests 219/219, comment form 17/18 forrpm.spec's upstream directives, LF 34/34), and the independent reviews of9ca629a,c5f36bc,36be7f2and9f5c935with their fixes, are in #544 (comment). Not claimed ready: merge prerequisites 1 and 4 are maintainer settings.