Skip to content

2026-07-22: Actionlint workflow gate (#612), appeal-issue close-out (#659, #660, #661) โ€‹

What shipped โ€‹

  • Pinned actionlint gate (#612). tooling/scripts/lint.actionlint.mjs lints .github/workflows/*.yml with actionlint v1.7.12, the version confirmed during the #607 incident to flag the exact empty $ expression that broke workflow parsing on dev. Wired three ways: npm run lint:actionlint, the validate orchestrator (scope workflows, next to Workflow Consistency), and a CI step in the Lint & Security job.
  • Supply-chain posture. No new third-party GitHub Action and no unverified binary download: the script auto-installs through the Go module proxy, which verifies checksums against the sum.golang.org transparency log. CI's ubuntu runners have Go preinstalled. A dev machine without Go gets a warn-and-skip (CI is the enforcement backstop); CI=true makes a missing binary a hard failure. Shellcheck integration is disabled (-shellcheck=) to keep the gate scoped to Actions expressions, per the issue's keep-scope-tight note.
  • One real finding fixed. actionlint flagged release-prod.yml's workflow_dispatch choice input containing an empty-string option (''). Replaced with a none sentinel and taught the consuming guard to treat none like unset. Behavior is identical for both trigger paths (workflow_run supplies no inputs; dispatch defaults to none).

Verified โ€‹

  • Clean run over all current workflows passes.
  • A probe workflow containing the #607 empty-$ class fails the gate and names the file.
  • No-binary local run warn-skips (exit 0); no-binary CI run hard-fails (exit 1).

Issue close-outs (stale-complete, no code needed) โ€‹

Scoping the appeal-hardening theme (#659, #660) found ALL functional checklist items already shipped on dev by the Stage C appeal build; both issues predate it and went stale. Closed with per-item mapping comments on the issues (docRequested end-to-end, 45-day aging floor server + admin, appeal per-number cap consumed only after proven OTP + confirmed restriction). Notables recorded there: the verify-otp path deliberately counts attempts BEFORE checkOtp as its brute-force bound (10/10min), and the claim-enum item was superseded by the moderator's action + docRequested carrying the classification. #661 (sync-skills strict YAML) was likewise already built (validateFrontmatter + non-zero exit); closed with mapping.

Deferred (recorded) โ€‹

  • Aging-floor badge on the admin appeals queue ROW (drawer already carries the signal at the decision point); noted in #660's close-out comment.
  • Script-wiring audit for lint:actionlint: grouped (Linting), described (scripts-info); no curated extension button on purpose, consistent with sibling CI-gate lints.

Built with VitePress