From a5bf3e2bfbb4ab742cbab8472520bf263d182c87 Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 18 May 2026 08:03:16 +0100 Subject: [PATCH 1/2] fix(ci): repair 3 invalid workflows rejected at parse time (Refs #77) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cargo-audit.yml, secret-scanner.yml and dogfood-gate.yml were rejected by GitHub's workflow validator on every push since they were added: runs completed as `failure` in 0 s with no jobs and no logs (name == file path == GitHub's "invalid workflow file" signature). Root causes: - cargo-audit.yml: `if: hashFiles('Cargo.lock') != ''` at JOB level. hashFiles() is not available in a job-level `if:` — only step-level contexts. Replaced with a step-level Cargo.lock detection guard. - secret-scanner.yml: same illegal job-level `if: hashFiles(...)` on the `rust-secrets` job. Replaced with a step-level detection guard. - dogfood-gate.yml: inline `python3 -c "..."` heredoc was dedented to column 0, breaking out of the `run: |` block scalar (YAML scanner error at line 261). Rewritten as an indented quoted heredoc that is sed-dedented before reaching the interpreter; validation logic and exit semantics are unchanged. All three now pass `python -c yaml.safe_load` and actionlint cleanly. Unjams the perpetual instant-failure of these checks on every push. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/cargo-audit.yml | 16 +++++++-- .github/workflows/dogfood-gate.yml | 49 +++++++++++++++------------- .github/workflows/secret-scanner.yml | 12 ++++++- 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/.github/workflows/cargo-audit.yml b/.github/workflows/cargo-audit.yml index bf6fd47c..19424271 100644 --- a/.github/workflows/cargo-audit.yml +++ b/.github/workflows/cargo-audit.yml @@ -21,23 +21,35 @@ jobs: audit: name: Dependency audit runs-on: ubuntu-latest - if: hashFiles('Cargo.lock') != '' steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - name: Detect Cargo.lock + id: detect + run: | + if [ -f Cargo.lock ]; then + echo "present=true" >> "$GITHUB_OUTPUT" + else + echo "present=false" >> "$GITHUB_OUTPUT" + echo "No Cargo.lock — skipping dependency audit." >> "$GITHUB_STEP_SUMMARY" + fi + - name: Install Rust toolchain + if: steps.detect.outputs.present == 'true' uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable - name: Install cargo-audit + if: steps.detect.outputs.present == 'true' run: cargo install cargo-audit --locked - name: Run cargo audit + if: steps.detect.outputs.present == 'true' run: cargo audit - name: Write summary - if: always() + if: always() && steps.detect.outputs.present == 'true' run: | echo "## Cargo Audit Results" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index 680b1b25..9547a131 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -256,31 +256,34 @@ jobs: echo "has_manifest=true" >> "$GITHUB_OUTPUT" - # Validate TOML structure using Python 3.11+ tomllib - python3 -c " -import tomllib, sys -with open('eclexiaiser.toml', 'rb') as f: - data = tomllib.load(f) -project = data.get('project', {}) -if not project.get('name', '').strip(): - print('ERROR: project.name is required', file=sys.stderr) - sys.exit(1) -functions = data.get('functions', []) -if not functions: - print('ERROR: at least one [[functions]] entry is required', file=sys.stderr) - sys.exit(1) -for fn in functions: - if not fn.get('name', '').strip(): - print('ERROR: function name cannot be empty', file=sys.stderr) - sys.exit(1) - if not fn.get('source', '').strip(): - print(f'ERROR: function {fn[\"name\"]} has no source path', file=sys.stderr) - sys.exit(1) -print(f'Valid: {project[\"name\"]} ({len(functions)} function(s))') -" || { + # Validate TOML structure using Python 3.11+ tomllib. + # Heredoc body is dedented before Python sees it so the YAML + # block-scalar indentation never reaches the interpreter. + if ! sed 's/^ //' <<'PY' | python3 - + import tomllib, sys + with open('eclexiaiser.toml', 'rb') as f: + data = tomllib.load(f) + project = data.get('project', {}) + if not project.get('name', '').strip(): + print('ERROR: project.name is required', file=sys.stderr) + sys.exit(1) + functions = data.get('functions', []) + if not functions: + print('ERROR: at least one [[functions]] entry is required', file=sys.stderr) + sys.exit(1) + for fn in functions: + if not fn.get('name', '').strip(): + print('ERROR: function name cannot be empty', file=sys.stderr) + sys.exit(1) + if not fn.get('source', '').strip(): + print(f'ERROR: function {fn["name"]} has no source path', file=sys.stderr) + sys.exit(1) + print(f'Valid: {project["name"]} ({len(functions)} function(s))') + PY + then echo "::error file=eclexiaiser.toml::Invalid eclexiaiser.toml — see step output for details" exit 1 - } + fi - name: Write summary run: | diff --git a/.github/workflows/secret-scanner.yml b/.github/workflows/secret-scanner.yml index fd62c4f8..352ab8dc 100644 --- a/.github/workflows/secret-scanner.yml +++ b/.github/workflows/secret-scanner.yml @@ -38,11 +38,21 @@ jobs: # Rust-specific: Check for hardcoded crypto values rust-secrets: runs-on: ubuntu-latest - if: hashFiles('**/Cargo.toml') != '' steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + - name: Detect Rust sources + id: detect + run: | + if find . -name Cargo.toml -print -quit | grep -q .; then + echo "present=true" >> "$GITHUB_OUTPUT" + else + echo "present=false" >> "$GITHUB_OUTPUT" + echo "No Cargo.toml — skipping Rust secret heuristics." >> "$GITHUB_STEP_SUMMARY" + fi + - name: Check for hardcoded secrets in Rust + if: steps.detect.outputs.present == 'true' run: | # Patterns that suggest hardcoded secrets PATTERNS=( From ac6852465e34c60e8df2e1f55a444a1087d77b83 Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 18 May 2026 08:10:17 +0100 Subject: [PATCH 2/2] fix(ci): make dogfood-gate Python heredoc survive YAML block-scalar dedent (Refs #77) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sed-based 16-space dedent on the eclexiaiser.toml validator was itself broken: GitHub strips the run-block's 10-space common indent before the script reaches the shell, so the heredoc body arrives at 6 leading spaces while the sed pattern still looks for 16 — nothing is stripped and python3 raises IndentationError on every push (echidna ships an eclexiaiser.toml, so this path is always hit). Drop the sed dedent entirely: place the Python body at the block-scalar base indentation so YAML's own dedent delivers it column-aligned to python3. Validation logic is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/dogfood-gate.yml | 47 ++++++++++++++++-------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index 9547a131..501ee675 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -257,28 +257,31 @@ jobs: echo "has_manifest=true" >> "$GITHUB_OUTPUT" # Validate TOML structure using Python 3.11+ tomllib. - # Heredoc body is dedented before Python sees it so the YAML - # block-scalar indentation never reaches the interpreter. - if ! sed 's/^ //' <<'PY' | python3 - - import tomllib, sys - with open('eclexiaiser.toml', 'rb') as f: - data = tomllib.load(f) - project = data.get('project', {}) - if not project.get('name', '').strip(): - print('ERROR: project.name is required', file=sys.stderr) - sys.exit(1) - functions = data.get('functions', []) - if not functions: - print('ERROR: at least one [[functions]] entry is required', file=sys.stderr) - sys.exit(1) - for fn in functions: - if not fn.get('name', '').strip(): - print('ERROR: function name cannot be empty', file=sys.stderr) - sys.exit(1) - if not fn.get('source', '').strip(): - print(f'ERROR: function {fn["name"]} has no source path', file=sys.stderr) - sys.exit(1) - print(f'Valid: {project["name"]} ({len(functions)} function(s))') + # The Python heredoc body sits at the YAML block-scalar base + # indentation, so after GitHub strips the block's common indent + # the interpreter receives it column-aligned (no sed dedent — + # a sed-based dedent breaks because YAML removes the block's + # leading indent before the script ever reaches the shell). + if ! python3 - <<'PY' + import tomllib, sys + with open('eclexiaiser.toml', 'rb') as f: + data = tomllib.load(f) + project = data.get('project', {}) + if not project.get('name', '').strip(): + print('ERROR: project.name is required', file=sys.stderr) + sys.exit(1) + functions = data.get('functions', []) + if not functions: + print('ERROR: at least one [[functions]] entry is required', file=sys.stderr) + sys.exit(1) + for fn in functions: + if not fn.get('name', '').strip(): + print('ERROR: function name cannot be empty', file=sys.stderr) + sys.exit(1) + if not fn.get('source', '').strip(): + print(f'ERROR: function {fn["name"]} has no source path', file=sys.stderr) + sys.exit(1) + print(f'Valid: {project["name"]} ({len(functions)} function(s))') PY then echo "::error file=eclexiaiser.toml::Invalid eclexiaiser.toml — see step output for details"