---
name: rsyslog_local_container_testing
description: Mirror rsyslog run_checks.yml container validation locally, including the change-gated Ubuntu 26.04 run-ci.sh check run, the clang static analyzer job, late prompt-based audit passes, service-skip validation, clean-tree rules, and container path caveats.
---

# rsyslog_local_container_testing

Use this skill when validating rsyslog implementation changes through local dev
containers, and treat it as the final pre-finish gate when Docker, Podman, or
equivalent container tooling is available. Local container validation should
mirror the relevant GitHub Actions container jobs, including their relevance
gates, instead of inventing a parallel local-only flow. If container tooling is
unavailable, report that limitation explicitly rather than silently
substituting a weaker gate.

## When To Use

Use this for PR-ready validation whenever local container support is available.
It is often faster than waiting for GitHub CI to discover environment-specific
failures, especially static-analyzer findings, compiler/toolchain differences,
dependency differences, generated build state issues, and service-test skip
behavior.

Focused host-side tests from `rsyslog_test` are still the right first step when
debugging a specific behavior. After the patch is stable, run this skill before
pushing if the change is intended for review.

## Pre-PR Testbed Tiers

Use these tiers as a session testbed before opening or updating PRs. The goal is
to catch the failures that otherwise cost a full GitHub Actions turnaround and
follow-up babysitting cycle, without cloning the entire CI matrix for every
small patch.

Start with the repository helper:

```sh
devtools/local-validation-plan.sh
```

The default mode classifies the current local diff and prints the recommended
validation path. It includes committed branch changes, staged and unstaged
tracked edits, and untracked files. This is important because PR work is often
validated before everything is committed.

When you want deterministic execution instead of a checklist, use:

```sh
devtools/local-validation-plan.sh --run
```

`--run` executes the selected path and exits on the first required failure.
Optional local linters still remain tool-presence guarded, so missing optional
developer tools do not turn a documentation or tooling-only change into a
container validation failure.

By default the helper compares committed branch changes against the worktree's
creation baseline: `RSYSLOG_LOCAL_VALIDATION_BASE` when set, otherwise
`rsyslog.localValidationBase` from git config when present, otherwise the
oldest `HEAD` reflog entry for the worktree. This keeps local diff
classification tied to the commit that was `HEAD` when the dedicated
worktree was created, even after `origin/main` moves. Use `--base REF` for an
intentional one-off override.

Before starting any heavy local validation, run the relevant cheap local checks
that are available in the environment. These checks should be diff-scoped and
tool-presence guarded:

- changed shell scripts: `shellcheck`
- changed scripts with a POSIX `sh` shebang: `checkbashisms -p`; on
  Debian/Ubuntu install it with `sudo apt-get install -y devscripts`. Do not
  run it as a full-testbench gate by default, because many tests intentionally
  use Bash-oriented testbench conventions. Keep this check diff-scoped so
  runtime stays proportional to the changed files.
- changed Python files: `devtools/format-python.sh --check-if-available`
- changed GitHub Actions workflows: `actionlint` and pinned `zizmor`
- changed Dockerfiles: `hadolint`
- changed infrastructure/config files: `trivy config` on the changed paths or
  smallest relevant directory
- larger source/test PRs: `jscpd` as an advisory duplication check
- changed C sources or headers MUST run the exact read-only check:
  `devtools/format-code.sh --git-changed --check --check-if-available`. This
  is a read-only `clang-format-18` dry-run gate for deterministic validation;
  if the exact formatter is unavailable, it warns and leaves hosted CI or a
  fuller local environment to cover the gap. CI will not pass with improperly
  formatted C/H code. Use `devtools/format-code.sh --git-changed` separately
  when you intentionally want to rewrite local files.
- changed C sources or headers also get an advisory raw-allocation scan in
  `devtools/local-validation-plan.sh --run`. Treat matches for `malloc`,
  `calloc`, `realloc`, `strdup`, and `strndup` as review prompts; prefer
  rsyslog allocation helpers where practical, but inspect low-level exceptions
  before changing code.
- changed shell tests should run `devtools/check-test-antipatterns.sh` through
  the helper. Its findings are advisory prompts for flake-prone constructs such
  as fixed sleeps, port preselection, fixed ports, and ad-hoc assertions.

Do not run `cppcheck` routinely unless a maintainer explicitly asks for it; it
is too noisy for routine rsyslog PR validation.

- **Agent-documentation and skill-only changes**. If the diff is limited to
  `AGENTS.md`, `AGENTS.local.md`, `.agent/skills/**`, or `.codex/skills/**`,
  do not run the Ubuntu 26.04 container check or static analyzer merely because
  validation instructions changed. Validate these edits with normal text review,
  Markdown/link sanity checks where available, and a small command dry-run only
  when the edited instructions include command snippets whose syntax is
  uncertain. If the agent-doc change also edits code, testbench files,
  workflows, build files, or scripts, validate those touched areas normally.
- **User-facing documentation changes**. If the diff changes rendered
  user-facing docs, such as `doc/source/**` or Sphinx support files that affect
  that tree, do not run the Ubuntu 26.04 runtime check just because docs
  changed. Instead run a high-concurrency docs build:
  `./doc/tools/build-doc-linux.sh --clean --format html --jobs "${RSYSLOG_LOCAL_DOC_JOBS:-$(nproc)}"`.
  Use `--strict` for larger, structural, or navigation-heavy documentation
  edits. Internal docs that are not rendered into the user manual, such as
  `doc/ai/**`, repository agent guides, and skill files, do not require this
  docs build unless they also change rendered Sphinx inputs.
- **Tier 1: default PR-ready gate for code or testbench changes**. Run a
  change-gated Ubuntu 26.04 `devtools/run-ci.sh` check first, then the existing
  static-analyzer pass and late prompt-based audit passes. This is the normal
  local confidence gate for C,
  parser, module, runtime, `tests/*.sh`, `diag.sh`, `Makefile.am`,
  `configure.ac`, and `run_checks.yml` changes.
- **Tier 2: compile portability gate for non-trivial C/header changes**. Add
  the two build-only compile lanes from `run_checks.yml`:
  `clang21-ndebug` and `gcc15-gnu23-debug`. Use this when a change touches
  shared headers, compiler-sensitive code, generated C paths, configure/build
  inputs, or anything that could behave differently under debug/no-debug or
  GNU23 compilation.
- **Tier 3: risk-triggered specialist lanes**. Add only the lanes that match the
  touched area or risk profile:
  `ubuntu_26_san` for memory, parser, string, JSON, property, and bounds work;
  `ubuntu_26_tsan` for threading, queue, action lifecycle, or lock-order work;
  `ubuntu_26_imtcp_no_epoll` for imtcp and network input changes;
  `ubuntu_22_distcheck` or `kafka_distcheck_CI` for dist, autotools,
  packaging, or Kafka Makefile changes.

Keep macOS, full distro matrix, package builds, and service-backed matrix jobs
as CI-owned by default. Run them locally only when the change directly targets
that area and the local host can reproduce the required environment.

Keep i386 and Ubuntu 24 regular runtime coverage cloud-only by default. Run
them locally only when reproducing a lane-specific failure, validating their
workflow/container, or when a maintainer explicitly asks for that local lane.

Normal PR-ready local validation is change-gated. It must not force the full
test suite by default. Force full coverage only for daily, weekly, release,
flake-campaign, maintainer-requested, or relevance-gate validation work.

## Local Capacity Knobs

Use the local machine's configured capacity for broad local checks instead of
hard-coding this host's current limit into instructions. Unknown machines should
default conservatively:

```sh
: "${RSYSLOG_LOCAL_CHECK_JOBS:=10}"
: "${RSYSLOG_LOCAL_BUILD_JOBS:=10}"
export CI_MAKE_OPT="-j${RSYSLOG_LOCAL_BUILD_JOBS}"
export CI_MAKE_CHECK_OPT="-j${RSYSLOG_LOCAL_CHECK_JOBS}"
```

`RSYSLOG_LOCAL_CHECK_JOBS` is the normal local `make check` concurrency gauge.
It should be set by the user or machine profile, for example in `.bashrc`.
Deflake and overload experiments are prompt-driven and must use explicit
one-off `-jN` values instead of changing this normal validation knob.

## Late Prompt Audits

Run prompt-based audits late in the process, after cheap deterministic checks,
mock distcheck when needed, the change-gated Ubuntu 26.04 container run, and
the static analyzer have either passed or the implementation is otherwise
stable enough that no major rewrite is expected. This keeps review tokens spent
on the candidate that is likely to ship instead of on intermediate churn.

Do not launch `codex`, `copilot`, or another AI CLI from repository scripts.
The active agent must read the canned prompt and apply it to the current diff,
changed functions, and nearby lifecycle paths. When a separate reviewer session
or subagent is explicitly available, it may be used, but local validation must
not depend on unknown local AI tooling, authentication, or model availability.

Use these prompt passes when they match the change:

- changed C sources or headers: read
  `ai/rsyslog_memory_auditor/base_prompt.txt` and audit changed functions plus
  nearby allocation, ownership, and cleanup paths. For focused allocation
  changes, also consult `audit_leaks.txt`, `audit_lifecycle.txt`, and
  `audit_null_checks.txt` as needed.
- lock, queue, worker, thread, timer, atomic, shutdown, retry, or resource
  ownership changes: read `ai/rsyslog_bug_finder/base_prompt.txt` and perform a
  path-sensitive resource and concurrency audit, including lock ordering and
  branch-specific counterexamples for any non-clean findings.
- testbench, build, or distribution plumbing changes: perform a project
  standards audit: check `tests/Makefile.am` registration, `EXTRA_DIST`,
  "define at top, distribute unconditionally, register conditionally" patterns,
  RainerScript syntax in touched tests, and mock distcheck coverage.

Report the prompt audits in the validation summary: which prompts were applied,
which findings were fixed, which findings were dismissed with rationale, or
that the audit was clean. Skip prompt audits for documentation-only and
instruction-only changes unless the edited instructions themselves change audit
or validation behavior.

## Optional Cubic Review

The local `cubic` CLI can provide additional AI review when it applies. Cubic is a small
local shim that forwards the review request to the configured cloud AI service,
so it should run late on the stabilized candidate. It is a broad review pass,
not a fast compile detector.

- **Docs-only changes**: Cubic is usually not useful.
- **Code changes (`*.c`, `*.h`, grammar/parser/runtime/module logic)**: consider
  Cubic when the command is installed and reachable.
- **Test-only changes**: consider Cubic for non-trivial test logic, timing/sync
  changes, helper changes, or broad testbench behavior.
- **Workflow, build, and tooling changes**: consider Cubic when the change is
  non-trivial, behavior-affecting, security-sensitive, or large. Use
  `actionlint`, pinned `zizmor`, and the relevant local linters as the primary
  deterministic checks for workflow syntax/security.
- **Mixed or large changes**: consider Cubic.

Hosted Cubic or Gemini PR comments are useful additional feedback. Neither
local nor hosted AI review replaces local analyzer, build, or container checks.

## Extended Lane Triggers

Use extended lanes to target a plausible failure mode that the default
change-gated Ubuntu 26.04 check would not expose. Do not add lanes by habit.

- **Compile portability lanes (`clang21-ndebug`, `gcc15-gnu23-debug`)**:
  run for shared header changes, configure/build/autotools changes,
  compiler-sensitive C changes, macro/inline helper changes, generated C paths,
  debug/no-debug sensitive code, GNU23/C standard sensitivity, or broad C
  changes where one normal compiler is weak evidence.
- **SAN (`ubuntu_26_san`)**: run for parser, string, buffer, JSON, property,
  length accounting, allocation, ownership, malformed-input, crash,
  undefined-behavior, or security-adjacent changes.
- **TSAN (`ubuntu_26_tsan`)**: run for queues, actions, worker lifecycle,
  shutdown, retry, timers, locks, atomics, condition variables, shared state,
  readiness/polling/synchronization test changes, and all changes to
  `runtime/wti.*` or `runtime/wtp.*`.
- **imtcp poll/select (`ubuntu_26_imtcp_no_epoll`)**: run when touching imtcp
  behavior that could differ between epoll and poll/select mode.
- **Distribution checks (`ubuntu_22_distcheck`, mock distcheck, or
  `kafka_distcheck_CI`)**: run when adding, renaming, or deleting files/tests,
  changing `Makefile.am`, `configure.ac`, `m4`, packaging lists, generated
  artifacts, or Kafka build/test plumbing. The local validation helper runs the
  fast `make distcheck TEST_RUN_TYPE=MOCK-OK` variant for changed, added,
  renamed, or deleted test and build-manifest distribution risks before
  escalating to heavier container lanes.
- **Service-specific lanes**: run only when directly relevant to the touched
  module, tests, helpers, build plumbing, or relevance logic. This includes
  Kafka, Elasticsearch, VictoriaLogs/omhttp, MySQL/libdbi, imfile, and similar
  focused families. For imjournal, local runtime tests are normally not
  representative unless the host has a usable journal environment; document the
  limitation and rely on CI/static checks when local runtime coverage is not
  practical.
- **i386 and Ubuntu 24**: leave cloud-only by default. Use local runs only for
  lane-specific reproduction, explicit 32-bit ABI/width work, dependency or
  Ubuntu-LTS package behavior, or workflow/container changes in those lanes.

## Preferred Order

For the PR-ready final gate, run two container validations for broad local
confidence. Mirror these
`.github/workflows/run_checks.yml` paths:

1. The `clang static analyzer` job's container command.
2. The change-gated Ubuntu 26.04 `devtools/run-ci.sh` check run, only after the
   analyzer results are clean or their findings are understood.

This sequence is what "PR-ready local container validation" means in agent
status reports. It is intentionally not an unconditional full-suite run; it
must use the same conservative change-gating and service-relevance model as
regular PR CI. A focused `devtools/run-ci.sh TEST=...` or build-only
`TESTS=""` container command is useful supplemental evidence, but it is only a
targeted container test unless this skill explicitly calls that reduced lane
acceptable for the touched area. Hosted CI, including hosted Cubic/Gemini
comments, does not replace this local container gate.

Keep runtimes and summarize failures separately for each run.

Use the skill's configured dev image or another explicit CI-equivalent image.
The Docker Hub `rsyslog/rsyslog_dev_base_ubuntu:26.04` image is acceptable for
normal local dev-container validation. When the task specifically validates a
locally built runtime or dev image, use that locally built image/tag for the
container-under-test and record its image ID.

For normal local validation, do not set `RSYSLOG_CONTAINER_UID=''`. Leaving
`RSYSLOG_CONTAINER_UID` unset lets `devtools/devcontainer.sh` run the container
process as the host uid/gid and inject a matching passwd/group entry when
needed. This prevents generated build products from being owned by the dev
image's default user, which is often a different uid than the host checkout
owner. Set `RSYSLOG_CONTAINER_UID=''` only when intentionally reproducing the
exact GitHub Actions default-container-user behavior, and expect to normalize
ownership afterwards.

## Clean Tree Rule

Before switching compiler, sanitizer flags, configure options, container image,
or test mode, clean generated build state:

```sh
make distclean || true
rm -f tests/runtime_unit_gss_token_util \
  tests/runtime_unit_linkedlist \
  tests/runtime_unit_stringbuf
```

Skip this only for an immediate rerun with unchanged settings.

If a previous container run left generated files owned by root or by an
unexpected container UID, `make distclean` may print permission errors or the
next `autoreconf` may fail while opening `autom4te.cache`. In that case,
normalize ownership and writable bits in the throwaway worktree before cleaning
again:

```sh
sudo chown -R "$(id -u):$(id -g)" .
chmod -R u+rwX,go+rwX .
make distclean || true
```

Use this only for local validation worktrees where the generated build output is
not part of the intended diff. Re-check `git status --short` afterwards and
remove only untracked build products or logs, not source changes.

Container configure runs leave generated Makefiles with absolute paths from
inside the container, such as `/rsyslog/missing`. Reconfigure on the host before
running host-side `make` in the same tree.

## Run 1: run_checks.yml Clang Static Analyzer

Clean before analyzer mode so `scan-build` recompiles the tree:

```sh
make distclean || true
rm -rf scan-build-report clang-analyzer.log
```

Run:

```sh
RSYSLOG_DEV_CONTAINER='rsyslog/rsyslog_dev_base_ubuntu:26.04' \
SCAN_BUILD='scan-build' \
SCAN_BUILD_CC='clang' \
SCAN_BUILD_REPORT_DIR='scan-build-report' \
CI_MAKE_OPT='-j20' \
DOCKER_RUN_EXTRA_OPTS='-e SCAN_BUILD -e SCAN_BUILD_CC -e SCAN_BUILD_REPORT_DIR' \
RSYSLOG_CONFIGURE_OPTIONS_EXTRA='--disable-elasticsearch --disable-elasticsearch-tests --disable-imkafka --disable-omkafka --disable-kafka-tests --disable-mysql --disable-mysql-tests' \
devtools/devcontainer.sh --rm devtools/run-static-analyzer.sh 2>&1 | tee clang-analyzer.log
```

The analyzer job intentionally has its own concurrency knob. In `run_checks.yml`
it uses `CI_MAKE_OPT='-j20'`; do not infer this from the broad compile matrix.

## Optional Tier 2: run_checks.yml Compile Matrix

Run these before PR for non-trivial C/header or portability-sensitive changes.
They are build-only checks and normally finish faster than a failed PR cycle.
Clean generated state before switching between configurations.

For `clang21-ndebug`:

```sh
make distclean || true
/usr/bin/time -p env \
RSYSLOG_DEV_CONTAINER='rsyslog/rsyslog_dev_base_ubuntu:26.04' \
CI_CONFIGURE_CACHE='1' \
CC='clang-21' \
CFLAGS='-g' \
RSYSLOG_CONFIGURE_OPTIONS_EXTRA='--enable-debug=no' \
devtools/devcontainer.sh --rm devtools/run-configure.sh
/usr/bin/time -p env \
RSYSLOG_DEV_CONTAINER='rsyslog/rsyslog_dev_base_ubuntu:26.04' \
devtools/devcontainer.sh --rm make -j20
```

For `gcc15-gnu23-debug`:

```sh
make distclean || true
/usr/bin/time -p env \
RSYSLOG_DEV_CONTAINER='rsyslog/rsyslog_dev_base_ubuntu:26.04' \
CI_CONFIGURE_CACHE='1' \
CC='gcc-15' \
CFLAGS='-g -std=gnu23' \
RSYSLOG_CONFIGURE_OPTIONS_EXTRA='--enable-debug=yes --disable-omamqp1' \
devtools/devcontainer.sh --rm devtools/run-configure.sh
/usr/bin/time -p env \
RSYSLOG_DEV_CONTAINER='rsyslog/rsyslog_dev_base_ubuntu:26.04' \
devtools/devcontainer.sh --rm make -j20
```

## Run 2: run_checks.yml Ubuntu 26.04 Check

Use the same `devtools/run-ci.sh` path as CI, with local concurrency from
`RSYSLOG_LOCAL_BUILD_JOBS` and `RSYSLOG_LOCAL_CHECK_JOBS`. For Valgrind-capable
check runs, set a non-sanitizer compiler and CFLAGS; ASAN-instrumented binaries
are not suitable for Valgrind tests.

For PR-ready validation with the actual changed-file set, apply the default
service relevance suppressions before entering the container. Local validation
normally happens before committing, so include committed branch changes,
staged/unstaged tracked changes, and untracked files. Clean generated build
state first so untracked build products do not pollute the relevance decision:

```sh
make distclean || true
: "${RSYSLOG_LOCAL_CHECK_JOBS:=10}"
: "${RSYSLOG_LOCAL_BUILD_JOBS:=10}"
export RSYSLOG_DEV_CONTAINER='rsyslog/rsyslog_dev_base_ubuntu:26.04'
export RSYSLOG_TESTBENCH_CHANGED_FILES="$({
  git diff --name-only origin/main...HEAD
  git diff --name-only HEAD
  git ls-files --others --exclude-standard
} | sort -u)"
export CC='gcc'
export CFLAGS='-g'
export CI_CONFIGURE_CACHE=1
export CI_MAKE_OPT="-j${RSYSLOG_LOCAL_BUILD_JOBS}"
export CI_MAKE_CHECK_OPT="-j${RSYSLOG_LOCAL_CHECK_JOBS}"
export CI_CHECK_CMD='check'
export VERBOSE=1
. devtools/apply-service-relevance.sh
rsyslog_apply_default_pr_service_suppressions
/usr/bin/time -p env \
devtools/devcontainer.sh --rm devtools/run-ci.sh
```

For service-skip validation with an irrelevant fake change set, set
`RSYSLOG_TESTBENCH_CHANGED_FILES` to a representative path such as
`doc/README.md` before sourcing `devtools/apply-service-relevance.sh`.

For a faster build-only smoke check, set:

```sh
CI_MAKE_CHECK_OPT="-j${RSYSLOG_LOCAL_CHECK_JOBS} TESTS="
```

This is useful for intermediate feedback, but it does not satisfy the PR-ready
validation gate.

Do not replace this run with a focused `TEST=...` lane just because the PR has a
single new test. The change-gated broad run validates the CI configure path,
generated state, service relevance filters, and broad testbench integration.

## Optional Tier 3: Risk-Triggered Specialist Lanes

Run these only when the changed area justifies the extra local time. The command
shape should stay close to `run_checks.yml`; use the trigger table above and
record deviations in the final status.

- **Memory-sensitive changes**: mirror `ubuntu_26_san` with `clang-21`,
  AddressSanitizer, UndefinedBehaviorSanitizer, disabled Valgrind, and the same
  configure extras. Use for parser, property, string, JSON, bounds, and
  segfault-related work.
- **Threading changes**: mirror `ubuntu_26_tsan` with `clang-21`,
  ThreadSanitizer, `--security-opt seccomp=unconfined`, and the CI TSAN
  suppressions. Use for queues, worker lifecycle, action shutdown, locks,
  shared state, and all changes to `runtime/wti.*` or `runtime/wtp.*`.
- **imtcp select-mode changes**: mirror `ubuntu_26_imtcp_no_epoll` when touching
  imtcp behavior that could differ between epoll and poll/select.
- **32-bit or ABI-sensitive changes**: rely on hosted i386 CI by default.
  Mirror `i386_CI` locally only when reproducing an i386-only failure, changing
  the i386 workflow/container, or when a maintainer explicitly requests local
  32-bit validation.
- **Distribution or build-system changes**: mirror `ubuntu_22_distcheck` for
  general dist/autotools risk, and `kafka_distcheck_CI` for Kafka build/test
  plumbing.

Do not run these by habit. The expected value is highest when the lane is tied
to the changed subsystem or to a specific failure mode seen during babysitting.

## Limited Local Environment Fallback

Some agents, workstations, and external contributor environments have limited
tooling. Web Agents commonly lack Docker/Podman, but the same situation can
happen on developer machines, locked-down CI shells, fresh containers, or
systems without optional linters. These environments are supported: missing
local tools are coverage gaps, not local workflow blockers. In that
environment:

- Prefer `devtools/local-validation-plan.sh` in default plan mode when a local
  checkout and shell are available. It can classify the change without starting
  containers.
- Use `devtools/local-validation-plan.sh --run` only when the selected class is
  runnable without containers, such as agent-doc-only, internal-doc-only,
  local-validation-tooling, or rendered-docs when the docs toolchain exists. Do
  not use a no-container `--run` result as substitute evidence for code,
  testbench, workflow, or focused container-test changes.
- Run every available applicable local check, such as diff-scoped linters,
  `actionlint`, pinned `zizmor`, Python style checks, shell syntax checks,
  documentation builds, host builds, focused host-side tests, and any
  repository scripts that work without containers. Missing one optional tool
  does not justify skipping other checks that are present.
- Inspect the changed files, `run_checks.yml`, and service relevance rules to
  identify the CI lanes that should cover the change.
- Do not claim PR-ready container validation. State clearly that container
  validation is unavailable in the current environment.
- Provide the exact local container lanes or hosted CI lanes that a container
  capable agent should run next.
- Missing local tools such as `shellcheck`, `checkbashisms`, `actionlint`,
  `zizmor`, `hadolint`, `trivy`, `jscpd`, `pycodestyle`, Docker, or
  Podman are not fatal by themselves. Report the missing checks and continue
  with all deterministic checks that are available and relevant.
- Local Cubic review is optional and does not affect validation completeness.

## Service Relevance

Regular PR CI may use approximate relevance gates to avoid scheduling expensive
service-backed test families that cannot reasonably be affected by the changed
files. The preferred result is that irrelevant families are absent from the
configured Automake `TESTS` set. Do not rely on starting an expensive test and
letting it skip late after service setup unless that is the only available
plumbing for that family.

Use `RSYSLOG_TESTBENCH_CHANGED_FILES` for local fake change sets when validating
the decision logic. Source `devtools/apply-service-relevance.sh` and call
`rsyslog_apply_default_pr_service_suppressions` before `devtools/run-ci.sh` to
apply the same PR-style configure suppressions in local container runs:

```sh
make distclean || true
export RSYSLOG_DEV_CONTAINER='rsyslog/rsyslog_dev_base_ubuntu:26.04'
export RSYSLOG_TESTBENCH_CHANGED_FILES='runtime/lookup.c'
export CC='gcc'
export CFLAGS='-g'
export CI_CONFIGURE_CACHE=1
export CI_MAKE_OPT='-j20'
export CI_MAKE_CHECK_OPT='-j10'
export CI_CHECK_CMD='check'
export VERBOSE=1
. devtools/apply-service-relevance.sh
rsyslog_apply_default_pr_service_suppressions
devtools/devcontainer.sh --rm devtools/run-ci.sh
```

For a fast scheduling proof, add `TEST_RUN_TYPE=MOCK-OK` and pass it into the
container via `DOCKER_RUN_EXTRA_OPTS='-e TEST_RUN_TYPE'`. The resulting
configure summary and `PASS:` list should show irrelevant heavy families
configured out rather than scheduled and skipped late.

For workflow or configure changes, validate both layers:

1. `tests/diag.sh module-needs-testing <family>` for representative changed
   files that should run and should skip the family.
2. A clean container/mock `devtools/run-ci.sh` path that confirms configure
   disables irrelevant test families and the generated test list omits their
   tests before execution.

The relevance check is intentionally conservative. Direct module changes,
related tests, testbench/build plumbing, workflow files, configure inputs, and
shared runtime paths that can plausibly affect a family must keep that family
enabled. Isolated helper-only changes, such as lookup tables or dynstats, may
avoid waking focused heavy families only when there is a clear code-path
rationale.

Full coverage must remain forceable. Use `RSYSLOG_TESTBENCH_FORCE_SERVICE_TESTS=1`,
`RSYSLOG_TESTBENCH_FORCE_<FAMILY>_TESTS=1`, or
`RSYSLOG_TESTBENCH_SKIP_SERVICE_RELEVANCE=1` when intentionally validating
service tests without relevant source changes, or when running daily, weekly,
release, flake-campaign, or maintainer-requested full coverage.

Allowed relaxations are narrow and tied to the touched area:

- imfile-only work may skip unrelated external service lanes.
- Kafka may be disabled only when neither Kafka modules nor Kafka test plumbing
  are touched.
- Elasticsearch may be disabled only when neither omelasticsearch nor
  Elasticsearch test plumbing are touched.
- Journal/imjournal runtime tests are static-analysis-only on hosts without a
  usable journal service. Do not try to force journal runtime tests in that
  environment; record the static-analysis exception instead.

Record every relaxation with the touched-area rationale. A skipped or relaxed
lane without that rationale makes the result targeted validation, not full
local container validation.

## Reporting

Report:

- exact command shape and container image
- image tag and image ID
- elapsed `real` time from `/usr/bin/time -p` where used
- pass/skip/fail summary from `tests/test-suite.log`
- whether skips were expected from relevance filtering
- lanes run, lanes relaxed/skipped, and touched-area rationale for each
  relaxation
- whether the result is fully container-validated or targeted
- unrelated local flakes separately from failures caused by the change

If a full container lane fails, inspect the logs and make up to four concrete
remediation attempts only when the failure appears locally fixable. Record the
command, log path, failure summary, each attempt, the affected PR/unit, and
whether host-side validation passed. If it still fails after those attempts,
carry the error forward in the session ledger instead of spinning.
