From 1612ac91bee18c14eb67ad7ef7abf9f233850183 Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 2 Jul 2026 05:20:41 +0100 Subject: [PATCH] ci: fix all four pre-existing workflow failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every one of these predates today's work; all four failed at workflow LOAD time (zero jobs), so nothing they gate has actually run in weeks: - secret-scanner.yml: caller granted only contents:read but the called reusable's gitleaks job requests pull-requests:write + actions:read. A called workflow can only narrow the caller's token, never exceed it -> startup_failure on every run since the 2026-06-24 repin (#62). Caller now grants the superset. (The reusable's comment claiming its permissions 'override the caller's' is backwards — flagged for standards separately.) - scorecard.yml: same class — read-all cannot cover the callee's security-events:write + id-token:write. Explicit grant block added. - dogfood-gate.yml: an inline python3 -c snippet was written at column 1 inside a run:| literal block, terminating the block scalar and making the entire file unparseable (path-as-name, zero jobs — all six jobs invisible). Script moved to the step's env.PYCODE block scalar (YAML strips base indentation there) and invoked as python3 -c "$PYCODE". - instant-sync.yml: secrets context is not available in step-level if: — workflow-file error at load. Secret hoisted to job env and the step gated on env.FARM_DISPATCH_TOKEN instead. (When the secret is absent the step skips and the job is green, which matches the recorded plan to drop FARM_DISPATCH_TOKEN after the credential rebuild.) Validated: actionlint clean across .github/workflows; all four parse with the expected job sets. Co-Authored-By: Claude Fable 5 --- .github/workflows/dogfood-gate.yml | 52 ++++++++++++++++------------ .github/workflows/instant-sync.yml | 8 ++++- .github/workflows/scorecard.yml | 10 +++++- .github/workflows/secret-scanner.yml | 7 ++++ 4 files changed, 53 insertions(+), 24 deletions(-) diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index bb1b944..029beea 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -249,6 +249,33 @@ jobs: - name: Check and validate eclexiaiser manifest id: eclex + # The validation script lives in env: rather than inline in run: + # because a YAML literal block ends at the first column-1 line — an + # unindented heredoc/py-snippet inside run: silently truncates the + # block and makes the whole workflow file unparseable (zero jobs, + # path shown as the workflow name). env: block scalars strip the + # base indentation, so Python receives clean unindented code. + env: + PYCODE: | + 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))') run: | if [ ! -f "eclexiaiser.toml" ]; then # Check if repo has a Containerfile — if so, recommend eclexiaiser @@ -261,28 +288,9 @@ 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 (script in + # the step's env.PYCODE — see comment above) + python3 -c "$PYCODE" || { echo "::error file=eclexiaiser.toml::Invalid eclexiaiser.toml — see step output for details" exit 1 } diff --git a/.github/workflows/instant-sync.yml b/.github/workflows/instant-sync.yml index 63321a4..d4a934d 100644 --- a/.github/workflows/instant-sync.yml +++ b/.github/workflows/instant-sync.yml @@ -12,9 +12,15 @@ jobs: dispatch: runs-on: ubuntu-latest timeout-minutes: 15 + # The `secrets` context is not available in step-level `if:` — using it + # there is a workflow-file error, so every run failed at load time with + # zero jobs. Hoist the secret into env (where `secrets` IS available) + # and gate the step on env instead. + env: + FARM_DISPATCH_TOKEN: ${{ secrets.FARM_DISPATCH_TOKEN }} steps: - name: Trigger Propagation - if: ${{ secrets.FARM_DISPATCH_TOKEN != '' }} + if: ${{ env.FARM_DISPATCH_TOKEN != '' }} uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v3 with: token: ${{ secrets.FARM_DISPATCH_TOKEN }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index e5dd237..72b3457 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -8,7 +8,15 @@ on: push: branches: [main] -permissions: read-all +# The called reusable's scorecard job requests security-events:write + +# id-token:write at job level. read-all cannot cover write scopes and a +# called workflow can only narrow the caller's token — so every run was +# a startup_failure. Grant exactly the superset the callee needs. +permissions: + contents: read + actions: read + security-events: write + id-token: write jobs: analysis: diff --git a/.github/workflows/secret-scanner.yml b/.github/workflows/secret-scanner.yml index d713d06..06de3f7 100644 --- a/.github/workflows/secret-scanner.yml +++ b/.github/workflows/secret-scanner.yml @@ -10,8 +10,15 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +# The called reusable's gitleaks job requests pull-requests:write + +# actions:read at job level. A called workflow can only NARROW the +# caller's token, never exceed it — granting only contents:read here +# made every run a startup_failure ("workflow file issue", zero jobs) +# since the 2026-06-24 repin. The caller must grant the superset. permissions: contents: read + pull-requests: write + actions: read jobs: scan: