From 31b7b4838c35389ddb7e52925f6e6ac362091c38 Mon Sep 17 00:00:00 2001 From: hushen <190065939+918154429@users.noreply.github.com> Date: Mon, 14 Sep 2026 23:42:38 +0800 Subject: [PATCH 1/2] fix(ci): discover local guards and report required skips --- .github/workflows/ci.yml | 3 + CHANGELOG.md | 6 ++ docs/developer-guide.md | 13 +++- scripts/ci-local.sh | 68 ++++++++++--------- tests/release/ci-local.test.sh | 116 +++++++++++++++++++++++++++++++++ 5 files changed, 173 insertions(+), 33 deletions(-) create mode 100755 tests/release/ci-local.test.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e25a1b0b..5f320add 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,6 +110,9 @@ jobs: - name: Check postgres-contract still requires both job tokens run: bash tests/release/postgres-contract-guard.test.sh + - name: Check local gate discovery and required-check warnings + run: bash tests/release/ci-local.test.sh + - name: Check the audit export states its confidentiality class run: bash tests/release/audit-export-confidentiality.test.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 26c93daa..5e96687f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ Releases before `0.2.5` predate the public launch; their notes live in the ## [Unreleased] +### Fixed + +- Discover release/e2e shell guards in local CI instead of maintaining a stale + list. Report skipped required Postgres validation in the final summary and + prefer Podman when both container runtimes are available (#346). + ## [0.15.0] — 2026-09-10 ### Changed diff --git a/docs/developer-guide.md b/docs/developer-guide.md index db1bff7b..cc82ee36 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -387,12 +387,12 @@ cargo nextest run --workspace --locked `scripts/ci-local.sh` mirrors the runnable jobs from `.github/workflows/ci.yml` (rust, frontend, hygiene, security, and the -optional postgres-contract job) so you catch failures before pushing, +required postgres-contract job) so you catch failures before pushing, without spending GitHub Actions minutes: ```sh -# Full run — everything CI runs, including the optional Postgres contract -# test if docker/podman is available (or SYSKNIFE_TEST_POSTGRES_URL is set) +# Full run — including the required Postgres contract when a URL or runtime +# is available (Podman is preferred over Docker) scripts/ci-local.sh # Fast subset — rust fmt/clippy/nextest + frontend tsc/vitest only @@ -402,6 +402,13 @@ scripts/ci-local.sh --fast scripts/ci-local.sh --no-postgres ``` +The hygiene group discovers every `tests/release/*.test.sh` and +`tests/e2e/*.test.sh`; there are no deliberate exclusions. A missing Postgres +runtime/URL or `--no-postgres` produces a final **INCOMPLETE** warning naming +the required gate. Set `SYSKNIFE_TEST_POSTGRES_URL` or install Podman and rerun +without `--no-postgres` to satisfy it. Skips do not change the exit code; actual +check failures still exit nonzero. + It detects which tools are installed first: `cargo` and `node` are required (missing either is a hard failure with an install link); an optional linter that's missing (`cargo-nextest`, `cargo-audit`, `markdownlint-cli2`, diff --git a/scripts/ci-local.sh b/scripts/ci-local.sh index f2b05652..01db6320 100755 --- a/scripts/ci-local.sh +++ b/scripts/ci-local.sh @@ -30,7 +30,7 @@ are caught before pushing (and before spending GitHub Actions minutes). --fast Rust fmt/clippy/nextest + frontend tsc/vitest only (the same subset the pre-push hook runs) - --no-postgres Skip the optional postgres-contract job even when a + --no-postgres Skip the required postgres-contract job even when a container runtime or SYSKNIFE_TEST_POSTGRES_URL is present --install-hooks Set git core.hooksPath to .githooks (enables the pre-push gate: scripts/ci-local.sh --fast) and exit -- does not @@ -38,7 +38,7 @@ are caught before pushing (and before spending GitHub Actions minutes). --help Show this help and exit Groups (default, full run): rust, frontend, hygiene, security, -postgres-contract (optional -- skipped if no runtime/URL is available). +postgres-contract (required in CI; a local skip leaves validation incomplete). For a full Docker-based replay of the exact GitHub Actions workflow (all jobs, exact runner image), see https://github.com/nektos/act instead. @@ -76,6 +76,7 @@ cd "$repo_root" RESULTS=() hard_failures=0 +required_skips=() have() { command -v "$1" >/dev/null 2>&1; } @@ -268,32 +269,27 @@ hygiene_shellcheck() ( | xargs -0 shellcheck --severity=warning ) +run_shell_tests() { + # No deliberate exclusions: every release/e2e *.test.sh is a local fixture + # or guard, not a live VM story. Keep any future exclusion here with its + # reason and an explicit SKIP outcome rather than maintaining a second list. + local test count=0 + for test in "$repo_root"/tests/release/*.test.sh "$repo_root"/tests/e2e/*.test.sh; do + [[ -f "$test" ]] || continue + count=$((count + 1)) + run_step "hygiene: ${test#"$repo_root/"}" bash "$test" + done + if ((count == 0)); then + record FAIL 'hygiene: no release/e2e shell tests discovered' + fi +} + run_hygiene_group() { printf '\n### hygiene\n' run_step 'hygiene: check_repo_completeness.sh' bash "$repo_root/scripts/check_repo_completeness.sh" run_step 'hygiene: check_release_versions.sh' bash "$repo_root/scripts/check_release_versions.sh" - run_step 'hygiene: release-version-pins.test.sh' bash "$repo_root/tests/release/release-version-pins.test.sh" - run_step 'hygiene: public-claims.test.sh' bash "$repo_root/tests/release/public-claims.test.sh" - run_step 'hygiene: test-baseline-provenance.test.sh' bash "$repo_root/tests/release/test-baseline-provenance.test.sh" run_step 'hygiene: npm test --prefix packages/setup' npm test --prefix "$repo_root/packages/setup" - run_step 'hygiene: registry-manifest.test.sh' bash "$repo_root/tests/release/registry-manifest.test.sh" - run_step 'hygiene: smithery-manifest.test.sh' bash "$repo_root/tests/release/smithery-manifest.test.sh" - run_step 'hygiene: release-rehearsal.test.sh' bash "$repo_root/tests/release/release-rehearsal.test.sh" - run_step 'hygiene: database-path-agreement.test.sh' bash "$repo_root/tests/release/database-path-agreement.test.sh" - run_step 'hygiene: node-eol.test.sh' bash "$repo_root/tests/release/node-eol.test.sh" - run_step 'hygiene: tracked-eol.test.sh' bash "$repo_root/tests/release/tracked-eol.test.sh" - run_step 'hygiene: systemd-directory-modes.test.sh' bash "$repo_root/tests/release/systemd-directory-modes.test.sh" - run_step 'hygiene: ubuntu-vm-bootstrap.test.sh' bash "$repo_root/tests/e2e/ubuntu-vm-bootstrap.test.sh" - run_step 'hygiene: provider-parity.test.sh' bash "$repo_root/tests/e2e/provider-parity.test.sh" - run_step 'hygiene: vm-env-secrets.test.sh' bash "$repo_root/tests/e2e/vm-env-secrets.test.sh" - run_step 'hygiene: story-metadata.test.sh' bash "$repo_root/tests/e2e/story-metadata.test.sh" - run_step 'hygiene: story-runner-verdicts.test.sh' bash "$repo_root/tests/e2e/story-runner-verdicts.test.sh" - run_step 'hygiene: vm-sync-parity.test.sh' bash "$repo_root/tests/e2e/vm-sync-parity.test.sh" - run_step 'hygiene: docs-share-cards.test.sh' bash "$repo_root/tests/release/docs-share-cards.test.sh" - run_step 'hygiene: install-paths.test.sh' bash "$repo_root/tests/release/install-paths.test.sh" - run_step 'hygiene: postgres-contract-guard.test.sh' bash "$repo_root/tests/release/postgres-contract-guard.test.sh" - run_step 'hygiene: audit-export-confidentiality.test.sh' bash "$repo_root/tests/release/audit-export-confidentiality.test.sh" - run_step 'hygiene: markdown-link-files.test.sh' bash "$repo_root/tests/release/markdown-link-files.test.sh" + run_shell_tests if have markdownlint-cli2; then run_step 'hygiene: markdownlint-cli2' hygiene_markdownlint @@ -342,15 +338,16 @@ run_security_group() { } # --------------------------------------------------------------------------- -# postgres-contract (optional) +# postgres-contract (required in CI) # --------------------------------------------------------------------------- run_postgres_contract_group() { - printf '\n### postgres-contract (optional)\n' + printf '\n### postgres-contract (required in CI)\n' local label="postgres-contract: live Postgres contract (store + CLI anchor exit code)" if [[ "$run_postgres" != true ]]; then record SKIP "${label} (--no-postgres)" + required_skips+=(postgres-contract) return fi @@ -366,14 +363,15 @@ run_postgres_contract_group() { fi local runtime="" - if have docker; then - runtime="docker" - elif have podman; then + if have podman; then runtime="podman" + elif have docker; then + runtime="docker" fi if [[ -z "$runtime" ]]; then record SKIP "${label} (no SYSKNIFE_TEST_POSTGRES_URL and no docker/podman found)" + required_skips+=(postgres-contract) return fi @@ -429,6 +427,7 @@ if [[ "$mode" == "full" ]]; then run_postgres_contract_group fi +print_summary() { printf '\n=========================================\n' printf ' ci-local summary (%s run)\n' "$mode" printf '=========================================\n' @@ -439,7 +438,16 @@ printf '=========================================\n' if ((hard_failures > 0)); then printf 'ci-local: FAIL (%d failing check(s))\n' "$hard_failures" - exit 1 fi -printf 'ci-local: PASS\n' +if ((${#required_skips[@]} > 0)); then + printf 'WARNING: REQUIRED CI check(s) did not run: %s\n' "${required_skips[*]}" + printf 'To run postgres-contract, set SYSKNIFE_TEST_POSTGRES_URL or install podman, then rerun without --no-postgres.\n' + printf 'ci-local: INCOMPLETE (required checks skipped)\n' +elif ((hard_failures == 0)); then + printf 'ci-local: PASS\n' +fi +} + +print_summary +((hard_failures == 0)) diff --git a/tests/release/ci-local.test.sh b/tests/release/ci-local.test.sh new file mode 100755 index 00000000..325526c1 --- /dev/null +++ b/tests/release/ci-local.test.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +# Exercise the local gate orchestration without running privileged operations, +# installing dependencies, or recursively executing the discovered test scripts. +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")/../.." +python3 - <<'PY' +import os +from pathlib import Path +import re +import subprocess +import tempfile +import unittest + +ROOT = Path.cwd() +SOURCE = (ROOT / 'scripts/ci-local.sh').read_text(encoding='utf-8') + + +def functions(*names): + result = [] + for name in names: + match = re.search(r'^' + name + r'\(\) \{\n.*?^\}', SOURCE, re.M | re.S) + if not match: + raise AssertionError('missing production function: ' + name) + result.append(match.group()) + return '\n'.join(result) + + +def bash(code, root=ROOT): + # Pass a POSIX path through the invoking shell, including under Git Bash. + env = dict(os.environ, TEST_ROOT=root.as_posix()) + return subprocess.run([os.environ.get('BASH_EXE', 'bash'), '-c', 'set -euo pipefail\nrepo_root="$TEST_ROOT"\n' + code], + env=env, text=True, capture_output=True) + + +class LocalGates(unittest.TestCase): + def test_hygiene_runs_every_discovered_and_ci_shell_test(self): + code = functions('run_hygiene_group') + # Include any extracted discovery function: the real hygiene entrypoint + # still decides whether to call it, so deleting that call fails this test. + if 'run_shell_tests() {' in SOURCE: + code += '\n' + functions('run_shell_tests') + code += ''' +have() { return 1; } +record() { :; } +run_step() { shift; if [[ "${1:-}" == bash && "${2:-}" == *.test.sh ]]; then printf '%s\\n' "${2#"$repo_root/"}"; fi; } +run_hygiene_group +''' + result = bash(code) + self.assertEqual(result.returncode, 0, result.stderr) + actual = [line for line in result.stdout.splitlines() if line.startswith('tests/')] + disk = {str(p.relative_to(ROOT)).replace('\\', '/') + for d in ('release', 'e2e') for p in (ROOT / 'tests' / d).glob('*.test.sh')} + ci = set() + for workflow in (ROOT / '.github/workflows').glob('*.yml'): + ci.update(re.findall(r'tests/(?:release|e2e)/[\w-]+\.test\.sh', workflow.read_text(encoding='utf-8'))) + self.assertTrue(disk, 'empty discovery must never pass') + self.assertTrue(ci, 'empty CI extraction must never pass') + self.assertEqual(set(actual), disk) + self.assertEqual(len(actual), len(set(actual)), 'a test must run exactly once') + self.assertFalse(ci - set(actual), 'CI tests missing locally: ' + str(ci - set(actual))) + + def test_new_file_is_discovered_and_empty_directory_fails(self): + code = functions('record', 'run_shell_tests') + ''' +RESULTS=(); hard_failures=0 +run_step() { printf '%s\\n' "$*"; } +run_shell_tests +printf 'failures=%s\\n' "$hard_failures" +''' + with tempfile.TemporaryDirectory() as d: + root = Path(d) + for directory in ('release', 'e2e'): + (root / 'tests' / directory).mkdir(parents=True) + result = bash(code, root) + self.assertIn('failures=1', result.stdout) + (root / 'tests/release/future-guard.test.sh').touch() + result = bash(code, root) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn('future-guard.test.sh', result.stdout) + self.assertIn('failures=0', result.stdout) + + def test_required_postgres_skip_is_in_final_summary(self): + code = functions('record', 'run_postgres_contract_group', 'print_summary') + code += ''' +RESULTS=(); hard_failures=0; required_skips=(); mode=full +run_postgres=true +unset SYSKNIFE_TEST_POSTGRES_URL +have() { return 1; } +run_postgres_contract_group +print_summary +''' + result = bash(code) + self.assertEqual(result.returncode, 0, result.stderr) + summary = result.stdout.split('ci-local summary', 1)[1] + for text in ('REQUIRED', 'postgres-contract', 'SYSKNIFE_TEST_POSTGRES_URL', 'podman', 'INCOMPLETE'): + self.assertIn(text, summary) + self.assertNotIn('ci-local: PASS', summary) + explicit = bash(code.replace('run_postgres=true', 'run_postgres=false')) + self.assertIn('INCOMPLETE', explicit.stdout) + + def test_podman_precedes_docker(self): + code = functions('record', 'run_postgres_contract_group') + ''' +RESULTS=(); hard_failures=0; required_skips=(); run_postgres=true +POSTGRES_CONTAINER_NAME=test; POSTGRES_HOST_PORT=5433 +unset SYSKNIFE_TEST_POSTGRES_URL +have() { return 0; } +podman() { printf 'podman %s\\n' "$*" >&2; return 1; } +docker() { printf 'docker was selected\\n' >&2; return 1; } +run_postgres_contract_group +''' + result = bash(code) + self.assertIn('podman run', result.stderr) + self.assertNotIn('docker was selected', result.stderr) + + +unittest.main(verbosity=2) +PY From 153998c3b8a8c6c3217cd71a32d7a7be17bef5c4 Mon Sep 17 00:00:00 2001 From: hushen <190065939+918154429@users.noreply.github.com> Date: Tue, 15 Sep 2026 00:18:00 +0800 Subject: [PATCH 2/2] fix: upgrade rustls for RUSTSEC-2026-0285 --- Cargo.lock | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 83cebc79..acd832f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -242,9 +242,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.3" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" dependencies = [ "aws-lc-sys", "zeroize", @@ -252,14 +252,15 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.40.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27" dependencies = [ "cc", "cmake", "dunce", "fs_extra", + "pkg-config", ] [[package]] @@ -4010,9 +4011,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.39" +version = "0.23.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" dependencies = [ "aws-lc-rs", "once_cell", @@ -4074,9 +4075,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "aws-lc-rs", "ring",