feat(workflows): reusable language CI + docker-publish + required aggregator#12
feat(workflows): reusable language CI + docker-publish + required aggregator#12WomB0ComB0 merged 10 commits intomainfrom
Conversation
… aggregator Introduce org-wide reusable workflows that consumer repos can call via `uses: resq-software/.github/.github/workflows/<name>@<sha>`: - rust-ci.yml: fmt, clippy, test (optional coverage), cargo-deny. - python-ci.yml: ruff, mypy, pytest matrix across 3.11/3.12/3.13, wheel build. - node-ci.yml: bun (default) / npm / pnpm / yarn with configurable lint, typecheck, test, build commands. - docker-publish.yml: buildx push to ghcr.io with SLSA build-provenance attestation (OIDC keyless, sigstore-backed). Emits image digest output. - required.yml: aggregator that dispatches to language CI by `lang` input, always runs the existing security-scan.yml, and emits a single `required` status-check context for Ruleset A. All third-party actions SHA-pinned with trailing `# <tag>` comment so Dependabot can still propose updates. Every job starts with step-security/harden-runner@f808768d (egress-policy: audit) matching the pattern already established in security-scan.yml. ops/governance-payloads.md documents the org-level PATCH/POST payloads for the custom-property schema, repo assignments, and two rulesets (baseline + critical-tier extras). Apply order: properties -> values -> rulesets. Rollback commands included. Deferred to follow-up: dotnet-ci.yml (only 1 consumer), cpp-ci.yml (2 consumers); required.yml will need a small edit when those land.
📝 WalkthroughWalkthroughAdds multiple reusable GitHub Actions workflows (Docker publish, Node, Python, Rust, C++, .NET) plus an aggregator Changes
Sequence Diagram(s)sequenceDiagram
participant Caller as Caller Workflow
participant Publish as docker-publish Workflow
participant Runner as GitHub Runner
participant Buildx as docker/build-push-action (Buildx)
participant Registry as Container Registry
participant Attest as actions/attest-build-provenance
Caller->>Publish: workflow_call(inputs: image, registry, tags, ...)
Publish->>Runner: start job (harden runner, checkout ref)
Runner->>Buildx: setup QEMU & Buildx, run build-push with inputs
Buildx->>Registry: push image(s) (if push=true) and return digest
Buildx-->>Publish: outputs.digest
Publish->>Attest: if attest && push -> create provenance attestation for subject registry/image@digest
Attest->>Registry: push attestation
Publish-->>Caller: output image-digest
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive guide and JSON payloads for managing GitHub organization governance, including custom property schemas and branch protection rulesets. Feedback was provided regarding the baseline ruleset, where increasing the required approving review count to one is recommended to prevent unreviewed merges in repositories without code owners. Additionally, the rollback instructions for disabling rulesets need correction, as the current PUT command lacks the full payload required to avoid configuration loss.
| { | ||
| "type": "pull_request", | ||
| "parameters": { | ||
| "required_approving_review_count": 0, |
There was a problem hiding this comment.
Setting required_approving_review_count to 0 allows pull requests to be merged with zero approvals if a repository lacks a CODEOWNERS file or if the changed files are not covered by it. This contradicts the warning at line 7; GitHub does not block PRs when a CODEOWNERS file is missing, it simply finds no owners to require a review from. To ensure a baseline of at least one review for all repositories, consider setting this value to 1.
| "required_approving_review_count": 0, | |
| "required_approving_review_count": 1, |
| gh api /orgs/resq-software/rulesets --jq '.[] | {id, name, enforcement}' | ||
|
|
||
| # Soft-disable | ||
| gh api --method PUT /orgs/resq-software/rulesets/<id> -f enforcement=disabled |
There was a problem hiding this comment.
The PUT method replaces the entire ruleset resource. Executing this command with only the enforcement field will likely fail because required fields (such as name) are missing, or it could inadvertently strip the ruleset of its configuration. To safely disable a ruleset, you should provide the full JSON payload with the enforcement field updated.
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (7)
.github/workflows/docker-publish.yml (1)
98-103: Skip registry login when not pushing.The login step runs unconditionally, but credentials are only needed when
inputs.pushis true. Gating it avoids unnecessary token exposure and a failure mode if a caller invokes the workflow withpush: falsefrom a context lackingpackages: writeonGITHUB_TOKEN.Proposed change
- name: Log in to registry + if: ${{ inputs.push }} uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/docker-publish.yml around lines 98 - 103, The "Log in to registry" step should be run only when pushing; add a conditional to that step (the step using docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121) so it only executes when inputs.push is true (e.g., use GitHub Actions conditional expression like ${{ inputs.push == 'true' }}), leaving the registry/username/password inputs unchanged..github/workflows/rust-ci.yml (1)
70-88:fmtjob ignoresinputs.componentsandinputs.timeout-minutes.The
fmtjob hardcodescomponents: rustfmt(line 87) andtimeout-minutes: 5(line 74) whileclippy/testuseinputs.componentsandinputs.timeout-minutes. Hardcodingrustfmthere is probably intentional (fmt only needs rustfmt), but the timeout asymmetry is worth either documenting or aligning with the input for consistency.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/rust-ci.yml around lines 70 - 88, The fmt job currently hardcodes "components: rustfmt" and "timeout-minutes: 5", causing asymmetry with other jobs that use inputs; update the fmt job to use the workflow input for timeout by replacing "timeout-minutes: 5" with "timeout-minutes: ${{ inputs.timeout-minutes }}" (keep "components: rustfmt" if intentional) or alternatively document in the job comment that fmt intentionally fixes components to rustfmt while timeout is shared via inputs; reference the job name "fmt" and the keys "components: rustfmt" and "timeout-minutes: 5" when making the change.ops/governance-payloads.md (5)
170-170: Emptybypass_actorsmeans even admins cannot bypass deployment requirements.Ruleset B has an empty bypass list, while Ruleset A allows admin bypass. This means for critical-tier repositories:
- Admins can bypass baseline rules (deletion, linear history, PR requirements) via pull requests
- Admins cannot bypass the production deployment requirement
This appears intentional for critical repos but is worth highlighting. If admins need emergency bypass capability for deployments, add a bypass actor here as well.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ops/governance-payloads.md` at line 170, Ruleset B's "bypass_actors" array is empty which prevents even admins from bypassing the production deployment requirement; if you need emergency admin bypass for critical-tier repositories, add the appropriate admin principal(s) (e.g., an admin user or group identifier) into the "bypass_actors" array for the Ruleset B entry so admins can bypass deployment checks when required while keeping the existing rules for other actions.
226-232: Add language identifier to the code block.The static analysis tool correctly flags that the code block should have a language specified for proper rendering.
📝 Proposed fix
-``` +```text ops/ properties-schema.json # JSON from §1 properties-values.json # JSON from §2 ruleset-a-baseline.json # JSON from §3 ruleset-b-critical.json # JSON from §4</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against the current code and only fix it if needed.
In
@ops/governance-payloads.mdaround lines 226 - 232, The fenced code block
showing the ops/ file listing in governance-payloads.md is missing a language
identifier; update the opening fence fromtotext so the block is
explicitly marked as plain text (i.e., replace the opening triple backticks
before the ops/ listing with ```text) to satisfy the static analysis rule.</details> --- `7-10`: **Warning is helpful; consider pre-flight checklist.** The warning correctly identifies that `require_code_owner_review` will block every PR on `resq-proto` until a `CODEOWNERS` file lands. The suggested mitigation (exclude from Ruleset A temporarily) is valid but adds operational complexity. Consider adding a pre-flight checklist item: verify that all repositories in scope (especially the 5 critical-tier repos) have `CODEOWNERS` files before applying Ruleset A, or plan a grace period with `enforcement: evaluate` initially. <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against the current code and only fix it if needed. In `@ops/governance-payloads.md` around lines 7 - 10, Add a pre-flight checklist and a safe rollout option: before enabling Ruleset A (which enforces require_code_owner_review), verify that all repositories in scope—especially the five critical-tier repos like resq-proto—have a CODEOWNERS file, and if any do not, either add CODEOWNERS or temporarily exclude that repo from Ruleset A's conditions.repository_name; alternatively, roll out with enforcement: evaluate (grace period) to detect blockages without blocking PRs, then switch to full enforcement once CODEOWNERS coverage is confirmed. ``` </details> --- `117-124`: **Clarify the intent of `required_approving_review_count: 0` with `require_code_owner_review: true`.** This configuration requires CODEOWNER review but sets the approval count to 0. This means: - A CODEOWNER must be requested as a reviewer - The CODEOWNER's review is not required to approve (they could comment/request changes without blocking merge) If the intent is to require CODEOWNER approval, set `required_approving_review_count: 1`. If the intent is CODEOWNER visibility without blocking, the current configuration is correct but worth documenting. <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against the current code and only fix it if needed. In `@ops/governance-payloads.md` around lines 117 - 124, The PR rule uses parameters.required_approving_review_count: 0 together with parameters.require_code_owner_review: true which requests CODEOWNER review but does not require their approval; decide and update the value accordingly: if you want CODEOWNER approval change parameters.required_approving_review_count to 1, otherwise add a comment in the governance payload clarifying that the current config only ensures CODEOWNER visibility (not blocking approval) and keep required_approving_review_count at 0; update the JSON near the "parameters" block and document the intent for future readers referencing required_approving_review_count and require_code_owner_review. ``` </details> --- `224-236`: **Consider providing the JSON files directly in the repository.** The current workflow requires manual extraction of JSON from Markdown code blocks, which is error-prone. Users could accidentally include: - Markdown code fences (```) - Trailing commas from multi-line formatting - Comments that aren't valid in JSON **Recommendation:** Add the four `.json` files to the `ops/` directory alongside this documentation. The Markdown would then serve as human-readable documentation and versioned source-of-truth, while the actual files are ready for `gh api --input` without manual intervention. Benefits: - Reduces user errors during extraction - Allows JSON validation in CI/linting - Enables direct usage: `gh api --method PATCH ... --input ops/properties-schema.json` - Keeps documentation and payloads in sync <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against the current code and only fix it if needed. In `@ops/governance-payloads.md` around lines 224 - 236, Create four standalone JSON files named ops/properties-schema.json, ops/properties-values.json, ops/ruleset-a-baseline.json, and ops/ruleset-b-critical.json that contain only the raw JSON bodies (no Markdown fences, comments, or trailing commas); update any `gh api --input` invocations in the docs or scripts to reference these files (e.g., `--input ops/properties-schema.json`), and add a CI lint/validation step to ensure the JSON files are syntactically valid so the docs (governance-payloads.md) remain the human-readable source while the repo provides ready-to-use payloads. ``` </details> </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary>Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/docker-publish.yml:
- Around line 65-68: Update the outputs.description for image-digest to document
that outputs.image-digest is only populated when the image is actually pushed
(i.e., when push: true for the docker/build-push-action), and modify the
outputs.value to fall back to steps.build.outputs.imageid when
jobs.publish.outputs.digest is empty; reference the existing
outputs.image-digest, jobs.publish.outputs.digest, steps.build.outputs.imageid
and the downstream attest step so callers know the digest may be empty if push:
false and to use the fallback when available.In @.github/workflows/node-ci.yml:
- Around line 95-122: The workflow exposes unquoted template inputs directly
into run blocks which can lead to script-injection and a missing default for the
package-manager case; fix by forwarding inputs into environment variables and
referencing them safely in the shell at runtime (e.g., set env: PM: ${{
inputs.package-manager }} and INST_CMD: ${{ inputs.install-cmd }} on the Install
step), then change the Install run to use case "$PM" in ... and include a
default *) echo "unknown package-manager: $PM" >&2; exit 1 ;; to fail on invalid
values; apply the same pattern for conditional steps (set env: CMD: ${{
inputs.lint-cmd }} / typecheck-cmd / build-cmd / test-cmd and run "$CMD") so all
user-provided commands are quoted and evaluated at runtime instead of being
interpolated into the composed script.- Around line 84-93: The workflow runs actions/setup-node with cache: 'pnpm'
before pnpm/action-setup, causing "Unable to locate executable file: pnpm";
reorder the steps so the pnpm bootstrap step (the pnpm/action-setup block with
run_install: false and its if: ${{ inputs.package-manager == 'pnpm' }}) executes
before the actions/setup-node block (the uses: actions/setup-node@... with
cache: ...), ensuring pnpm is on PATH when setup-node tries to compute the pnpm
cache.In @.github/workflows/python-ci.yml:
- Line 134: The workflow step hardcodes "uv run pytest tests/" which breaks
projects that rely on pytest discovery or custom test layouts; add a new
workflow input named test-paths (default "tests/") and replace the hardcoded
path with that input (or remove the explicit path entirely so pytest uses
pyproject.toml) in the step that currently runs "uv run pytest tests/" and
ensure you keep existing test-flags (the "test-flags" input) so the final
command uses either "uv run pytest ${{ inputs.test-paths }} ${{
inputs.test-flags }}" or "uv run pytest ${{ inputs.test-flags }}" if you prefer
discovery-only behavior.- Around line 86-88: Update the workflow run steps that interpolate inputs
directly into the shell to instead pass those inputs via env and reference the
env vars quoted in the run string: create environment variables (e.g.,
RUFF_TARGETS, MYPY_TARGETS, TEST_FLAGS) using env: RUFF_TARGETS: ${{
inputs.ruff-targets }} (and similarly for mypy and test flags) and change the
run lines such asuv run ruff check ${{ inputs.ruff-targets }}touv run ruff check "$RUFF_TARGETS"; apply the same pattern for thetypecheckandtest
job steps so all${{ inputs.* }}are set via env and used as"$VAR"in the
run commands to avoid direct expression-to-shell expansion.In @.github/workflows/required.yml:
- Around line 18-21: The workflow accepts a free-form lang input which can
silently skip all language jobs; add explicit validation in the required job to
fail fast on unknown values by checking the workflow input/env var "lang"
against the allowed set ("rust","python","node","polyglot") and exit non‑zero if
it doesn't match; implement the check as an early step in the "required" job
(use the job's env or a small shell step) so typos like "pyhton" cause the
required job to fail immediately rather than letting downstream language jobs
silently skip.In @.github/workflows/rust-ci.yml:
- Line 109: Replace direct interpolation of
${{ inputs.clippy-flags }}and
${{ inputs.test-flags }}in therun:steps by passing them through
environment variables and expanding them as quoted variables in the command to
prevent script-injection; specifically, set env entries (e.g., CLIPPY_FLAGS and
TEST_FLAGS) from the corresponding${{ inputs.* }}and update therun:lines
that callcargo clippyand the test command to use"$CLIPPY_FLAGS"and
"$TEST_FLAGS"instead of${{ inputs.* }}so the flags are safely quoted and
not interpreted as part of the shell.In
@ops/governance-payloads.md:
- Around line 67-84: The payload includes two repository entries with
repository_name "resQ" and "resq-proto" that do not exist; remove those objects
from the "properties" array or replace them with the correct repository names if
they were mistyped (e.g., correct casing or alternative repo id), making sure to
update both their repository_name values and associated property objects
(domain/tier/lang) in the JSON so the "properties" array only references valid
repositories.- Around line 157-179: The ruleset "critical-tier-extras" is enforcing a
required_deployments check for required_deployment_environments = ["production"]
but the listed repositories (only crates, npm, pypi exist; resQ and resq-proto
do not) lack a "production" deployment environment, so activation will block
PRs; either create a "production" deployment environment in each existing repo
(crates, npm, pypi) or modify the ruleset's parameters to target only actual
environments (or remove/adjust "production" in required_deployment_environments)
and/or update the repository list to remove nonexistent repos before enabling
the ruleset.
Nitpick comments:
In @.github/workflows/docker-publish.yml:
- Around line 98-103: The "Log in to registry" step should be run only when
pushing; add a conditional to that step (the step using
docker/login-action@4907a6d) so it only
executes when inputs.push is true (e.g., use GitHub Actions conditional
expression like ${{ inputs.push == 'true' }}), leaving the
registry/username/password inputs unchanged.In @.github/workflows/rust-ci.yml:
- Around line 70-88: The fmt job currently hardcodes "components: rustfmt" and
"timeout-minutes: 5", causing asymmetry with other jobs that use inputs; update
the fmt job to use the workflow input for timeout by replacing "timeout-minutes:
5" with "timeout-minutes: ${{ inputs.timeout-minutes }}" (keep "components:
rustfmt" if intentional) or alternatively document in the job comment that fmt
intentionally fixes components to rustfmt while timeout is shared via inputs;
reference the job name "fmt" and the keys "components: rustfmt" and
"timeout-minutes: 5" when making the change.In
@ops/governance-payloads.md:
- Line 170: Ruleset B's "bypass_actors" array is empty which prevents even
admins from bypassing the production deployment requirement; if you need
emergency admin bypass for critical-tier repositories, add the appropriate admin
principal(s) (e.g., an admin user or group identifier) into the "bypass_actors"
array for the Ruleset B entry so admins can bypass deployment checks when
required while keeping the existing rules for other actions.- Around line 226-232: The fenced code block showing the ops/ file listing in
governance-payloads.md is missing a language identifier; update the opening
fence fromtotext so the block is explicitly marked as plain text (i.e.,
replace the opening triple backticks before the ops/ listing with ```text) to
satisfy the static analysis rule.- Around line 7-10: Add a pre-flight checklist and a safe rollout option: before
enabling Ruleset A (which enforces require_code_owner_review), verify that all
repositories in scope—especially the five critical-tier repos like
resq-proto—have a CODEOWNERS file, and if any do not, either add CODEOWNERS or
temporarily exclude that repo from Ruleset A's conditions.repository_name;
alternatively, roll out with enforcement: evaluate (grace period) to detect
blockages without blocking PRs, then switch to full enforcement once CODEOWNERS
coverage is confirmed.- Around line 117-124: The PR rule uses
parameters.required_approving_review_count: 0 together with
parameters.require_code_owner_review: true which requests CODEOWNER review but
does not require their approval; decide and update the value accordingly: if you
want CODEOWNER approval change parameters.required_approving_review_count to 1,
otherwise add a comment in the governance payload clarifying that the current
config only ensures CODEOWNER visibility (not blocking approval) and keep
required_approving_review_count at 0; update the JSON near the "parameters"
block and document the intent for future readers referencing
required_approving_review_count and require_code_owner_review.- Around line 224-236: Create four standalone JSON files named
ops/properties-schema.json, ops/properties-values.json,
ops/ruleset-a-baseline.json, and ops/ruleset-b-critical.json that contain only
the raw JSON bodies (no Markdown fences, comments, or trailing commas); update
anygh api --inputinvocations in the docs or scripts to reference these files
(e.g.,--input ops/properties-schema.json), and add a CI lint/validation step
to ensure the JSON files are syntactically valid so the docs
(governance-payloads.md) remain the human-readable source while the repo
provides ready-to-use payloads.</details> <details> <summary>🪄 Autofix (Beta)</summary> Fix all unresolved CodeRabbit comments on this PR: - [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended) - [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes </details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: defaults **Review profile**: CHILL **Plan**: Pro **Run ID**: `15706f4b-c6b0-48eb-9640-be960642ec6f` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 9069479a1d876c4074b15073d0994e23ac4e8a03 and 5a723728feccfc5521dc4e5cb06237892d393553. </details> <details> <summary>📒 Files selected for processing (6)</summary> * `.github/workflows/docker-publish.yml` * `.github/workflows/node-ci.yml` * `.github/workflows/python-ci.yml` * `.github/workflows/required.yml` * `.github/workflows/rust-ci.yml` * `ops/governance-payloads.md` </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
First CI workflow for this repo. Calls the org-wide reusable `rust-ci.yml` (fmt + clippy + test) pinned to the PR#12 tip SHA. Adds a top-level `required` aggregator job to emit the status-check context consumed by the org ruleset `default-branch-baseline` (currently evaluate-mode). Security scanning remains in security.yml — independent cadence. After resq-software/.github#12 lands, update the @sha ref.
…l dispatch Adds the two deferred reusable workflows: - dotnet-ci.yml: restore, build, format check, test. Configurable dotnet-version (default 9.0.x), solution path, working-directory. - cpp-ci.yml: CMake configure + build + test matrix across ubuntu/macos/windows (configurable). Harden-runner on Linux only (step-security action is Linux-native). Extends required.yml to dispatch the two new languages, and adds proto handling (security-only, no language build dispatched). New inputs: dotnet-version, dotnet-solution, cpp-os-list, cpp-source-dir, cpp-cmake-flags. After this lands, these placeholder consumer PRs can be hardened to call real CI: - resq-software/dotnet-sdk#36 (dotnet) - resq-software/viz#4 (dotnet) - resq-software/vcpkg#6 (cpp) - resq-software/ardupilot#1 (cpp)
…e lang Addresses review feedback on PR #12 from CodeQL, CodeRabbit, and gemini-code-assist: Script-injection mitigations (CodeQL #1-15, #22-25; CodeRabbit): - node-ci.yml: forward install/lint/typecheck/build/test/package-manager via env: - python-ci.yml: forward ruff/mypy/test targets and flags via env: - rust-ci.yml: forward clippy-flags and test-flags via env: - cpp-ci.yml: forward source-dir, build-dir, cmake-flags via env: - docker-publish.yml: image-digest output doc-fixed (only populated when push:true) All affected steps now use `sh -c "$VAR"` rather than direct `${{ ... }}` interpolation, closing the template-time-to-shell expansion path. node-ci.yml critical ordering fix (CodeRabbit): - pnpm/action-setup now runs BEFORE actions/setup-node with cache: 'pnpm'; required because setup-node's pnpm cache detection resolves the pnpm binary. required.yml (CodeRabbit): - Added validate-lang gate that fails fast on unknown lang values. Prior to this, a typo like `pyhton` silently skipped every language job and `required` still passed. validate-lang is now a `needs:` dependency of every other job + the final `required` evaluator. python-ci.yml (CodeRabbit): - Added `test-paths` input (default empty = pytest discovers via pyproject.toml). Previous hardcoded `tests/` didn't match src-layout or discover-by-config projects. ops/governance-payloads.md (gemini-code-assist): - Rollback command for rulesets: PUT was stripping the resource because only `enforcement` was sent. Replaced with `gh api GET | jq .enforcement="disabled" | gh api PUT --input -`. Still open (not fixed here, requires org discussion): - Ruleset A `required_approving_review_count: 0` — gemini notes that without CODEOWNERS matching, PRs can merge with zero reviews. Current solo-dev intent; bump when team grows.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (5)
.github/workflows/required.yml (1)
101-207: Optional: addharden-runnertovalidate-langandrequiredfor policy consistency + small comment typo.The PR description states "every job starts with step-security/harden-runner in audit mode" — the two inline jobs here (
validate-lang,required) are the only ones that don't. They execute only bash built-ins with no network egress, so the security impact is minimal, but adding the step keeps the "every job" invariant true and avoids future drift. Also, Line 195 reads "typo defends handled there" — should be "typo defenses".♻️ Suggested tweaks
validate-lang: name: validate lang runs-on: ubuntu-latest steps: + - name: Harden Runner + uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2 + with: + egress-policy: audit - name: Validate lang inputrequired: name: required needs: [validate-lang, security, rust, python, node, dotnet, cpp] if: always() runs-on: ubuntu-latest steps: + - name: Harden Runner + uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2 + with: + egress-policy: audit - name: Evaluate upstream results- # validate-lang must be success — typo defends handled there + # validate-lang must be success — typo defenses handled thereThe validation gate + aggregator logic itself (Lines 196–207) looks correct: hard-failing on a non-success
validate-langbefore the permissive loop means the previously-raised "invalid lang silently passesrequired" concern is closed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/required.yml around lines 101 - 207, Add the missing harden-runner step and fix the typo: in the validate-lang job add the step-security/harden-runner (audit mode) as the first step so it runs for that inline job as with others, and in the required job add the same harden-runner step before "Evaluate upstream results"; also change the comment "typo defends handled there" to "typo defenses handled there" to correct the spelling. Ensure you reference the existing job names validate-lang and required and use the same step identifier (step-security/harden-runner) and audit-mode configuration as used in other jobs.ops/governance-payloads.md (4)
229-235: Add language identifier to code fence.The directory tree structure at line 229 uses a fenced code block without a language identifier, triggering a markdownlint warning. Specify
textorbashas the language.🎨 Proposed fix
-``` +```text ops/ properties-schema.json # JSON from §1 properties-values.json # JSON from §2As per coding guidelines, markdownlint-cli2 flags MD040 (fenced-code-language).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ops/governance-payloads.md` around lines 229 - 235, Update the fenced code block that shows the directory tree starting with "ops/" so it includes a language identifier (e.g., change the opening ``` to ```text or ```bash); locate the code fence that contains the lines beginning with "ops/" and add the language token to satisfy markdownlint MD040 while leaving the block contents unchanged.
210-214: Consider adding a safety check to the rollback command.The pipeline at lines 212-214 is correct but brittle—if the API response structure changes or contains unexpected fields, the modified payload might fail. Consider adding an intermediate inspection step.
🛡️ Safer rollback workflow
-# Soft-disable: PUT replaces the entire ruleset resource, so fetch the -# existing body, toggle `enforcement`, and re-submit the full object. -gh api /orgs/resq-software/rulesets/<id> \ - | jq '.enforcement = "disabled"' \ - | gh api --method PUT /orgs/resq-software/rulesets/<id> --input - +# Soft-disable: PUT replaces the entire ruleset resource, so fetch the +# existing body, toggle `enforcement`, and re-submit the full object. +# Step 1: Save current state to a file for inspection +gh api /orgs/resq-software/rulesets/<id> > /tmp/ruleset-<id>.json +# Step 2: Modify and review +jq '.enforcement = "disabled"' /tmp/ruleset-<id>.json > /tmp/ruleset-<id>-disabled.json +cat /tmp/ruleset-<id>-disabled.json # Review before applying +# Step 3: Apply +gh api --method PUT /orgs/resq-software/rulesets/<id> --input /tmp/ruleset-<id>-disabled.json🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ops/governance-payloads.md` around lines 210 - 214, Add a safety/validation step before the PUT to avoid submitting malformed or unexpected API responses: after fetching and mutating the ruleset with gh api and jq (the pipeline that sets '.enforcement = "disabled"'), pipe the result into a JSON validator or write it to a temporary file and inspect it (e.g., ensure the top-level object and required fields exist and that 'enforcement' is set) and only then call gh api --method PUT /orgs/resq-software/rulesets/<id> --input -; also consider adding an explicit confirm prompt or a CI-friendly dry-run check so the PUT is executed only after successful validation.
51-58: Ensure users extract JSON files before attempting to apply payloads.Lines 51-56 reference
ops/properties-schema.jsonvia--input, but the extraction instructions appear only at §7 (lines 225-239). While line 58 does remind users to extract first, the dependency could be more prominent to prevent errors.Consider adding a callout at the beginning of §1 (after line 16) that directs users to §7 first:
> **Prerequisite:** Extract JSON payloads to standalone files per §7 before running any apply commands.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ops/governance-payloads.md` around lines 51 - 58, Add an explicit prerequisite callout near the start of section 1 telling users to extract JSON payloads before running apply commands: reference the gh api command that uses --input and the file name ops/properties-schema.json, and instruct readers to follow the extraction steps in §7 (where payload extraction is described) so they extract JSON blocks into standalone files (e.g., ops/properties-schema.json) before executing gh api --input commands.
199-199: Clarify thatpypiis an example repository.Line 199 hardcodes
pypito demonstrate branch rule inspection, but readers might not realize it's arbitrary. Consider adding a comment or using a placeholder.📝 Suggested clarification
gh api /orgs/resq-software/properties/schema gh api /orgs/resq-software/properties/values gh api /orgs/resq-software/rulesets -gh api /repos/resq-software/pypi/rules/branches/main +gh api /repos/resq-software/pypi/rules/branches/main # Example: inspect rules on pypi's main branch🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ops/governance-payloads.md` at line 199, The example command "gh api /repos/resq-software/pypi/rules/branches/main" hardcodes "pypi" and should be clarified as an arbitrary example; update the line to either replace "pypi" with a clearly marked placeholder like "<REPO_NAME>" or add a short inline comment stating "'pypi' is an example repository name" so readers know to substitute their own repo when running the command.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/dotnet-ci.yml:
- Around line 64-67: Replace direct interpolation of ${{ inputs.solution }} and
${{ inputs.configuration }} inside run: for the "Restore" and "Build" steps (and
the other two run: occurrences flagged) by passing those values into env
variables (e.g., SOLUTION and CONFIGURATION) using env:, reference them inside
the run script as $SOLUTION and $CONFIGURATION (or appropriate shell variable
syntax) so the runner does not re-parse caller-controlled strings, and apply
this same env-indirection pattern to the other two run: lines flagged at lines
86 and 106; do not wrap the run command in sh -c "…" as that defeats the
mitigation.
In @.github/workflows/rust-ci.yml:
- Around line 102-105: The GitHub Actions steps use sh -c "cargo clippy $FLAGS"
(and similarly for cargo test) which re-parses the substituted string and
reintroduces shell injection; change the steps to invoke cargo directly (remove
sh -c) and pass $FLAGS unquoted so the runner performs word-splitting into argv
(e.g., replace the run command for the cargo clippy step (symbol: the "cargo
clippy" step) and the cargo test step (symbol: the "cargo test" step) to call
cargo directly without sh -c and with $FLAGS unquoted).
In `@ops/governance-payloads.md`:
- Around line 7-10: Update the warning in governance-payloads.md for Ruleset A
(which enforces require_code_owner_review) to list the actual repos missing
CODEOWNERS by replacing the `resq-proto` mention with `.github` and `ardupilot`;
ensure the text explains that either add CODEOWNERS to `.github` and `ardupilot`
or exclude those repository names from Ruleset A's `conditions.repository_name`
during the bake period so they don't block PRs.
---
Nitpick comments:
In @.github/workflows/required.yml:
- Around line 101-207: Add the missing harden-runner step and fix the typo: in
the validate-lang job add the step-security/harden-runner (audit mode) as the
first step so it runs for that inline job as with others, and in the required
job add the same harden-runner step before "Evaluate upstream results"; also
change the comment "typo defends handled there" to "typo defenses handled there"
to correct the spelling. Ensure you reference the existing job names
validate-lang and required and use the same step identifier
(step-security/harden-runner) and audit-mode configuration as used in other
jobs.
In `@ops/governance-payloads.md`:
- Around line 229-235: Update the fenced code block that shows the directory
tree starting with "ops/" so it includes a language identifier (e.g., change the
opening ``` to ```text or ```bash); locate the code fence that contains the
lines beginning with "ops/" and add the language token to satisfy markdownlint
MD040 while leaving the block contents unchanged.
- Around line 210-214: Add a safety/validation step before the PUT to avoid
submitting malformed or unexpected API responses: after fetching and mutating
the ruleset with gh api and jq (the pipeline that sets '.enforcement =
"disabled"'), pipe the result into a JSON validator or write it to a temporary
file and inspect it (e.g., ensure the top-level object and required fields exist
and that 'enforcement' is set) and only then call gh api --method PUT
/orgs/resq-software/rulesets/<id> --input -; also consider adding an explicit
confirm prompt or a CI-friendly dry-run check so the PUT is executed only after
successful validation.
- Around line 51-58: Add an explicit prerequisite callout near the start of
section 1 telling users to extract JSON payloads before running apply commands:
reference the gh api command that uses --input and the file name
ops/properties-schema.json, and instruct readers to follow the extraction steps
in §7 (where payload extraction is described) so they extract JSON blocks into
standalone files (e.g., ops/properties-schema.json) before executing gh api
--input commands.
- Line 199: The example command "gh api
/repos/resq-software/pypi/rules/branches/main" hardcodes "pypi" and should be
clarified as an arbitrary example; update the line to either replace "pypi" with
a clearly marked placeholder like "<REPO_NAME>" or add a short inline comment
stating "'pypi' is an example repository name" so readers know to substitute
their own repo when running the command.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 34704e1e-55e1-41b9-99f1-40b9fbcebec6
📒 Files selected for processing (8)
.github/workflows/cpp-ci.yml.github/workflows/docker-publish.yml.github/workflows/dotnet-ci.yml.github/workflows/node-ci.yml.github/workflows/python-ci.yml.github/workflows/required.yml.github/workflows/rust-ci.ymlops/governance-payloads.md
🚧 Files skipped from review as they are similar to previous changes (2)
- .github/workflows/python-ci.yml
- .github/workflows/node-ci.yml
Second pass addressing findings from CodeQL, CodeRabbit, and
gemini-code-assist that weren't caught in the first fix commit.
Critical (CodeQL) — dotnet-ci.yml script injection:
- `inputs.solution` and `inputs.configuration` were interpolated
directly into `run:` in restore/build/format/test steps. Fixed
the same way as the other lang CIs: forward through env: and
reference `"$VAR"` in sh -c.
- Closes CodeQL alerts 16-21 (dotnet-ci.yml).
Minor (CodeRabbit) — docker-publish.yml:
- `Log in to registry` step now gated on `if: inputs.push`. Avoids
unnecessary token exposure and an auth failure when a caller
invokes the workflow with `push: false` from a context without
`packages: write` on GITHUB_TOKEN.
Docs (CodeRabbit + gemini) — ops/:
- New: ops/properties-schema.json, ops/properties-values.json,
ops/ruleset-a-baseline.json, ops/ruleset-b-critical.json. Ready
for `gh api --input <file>` with no copy-paste from the markdown.
Validated as syntactically-valid JSON.
- governance-payloads.md rewritten:
* §4 (Ruleset B) synced with what was actually applied: dropped
`required_deployments: ["production"]` (no critical repo has
a production environment) and replaced with `require_last_push_approval: true`
+ `required_signatures`. Documented the rationale.
* Added pre-flight checklist with CODEOWNERS coverage check and
evaluate-mode bake period.
* Clarified intent of `required_approving_review_count: 0` +
`require_code_owner_review: true` (visibility, not approval
gate — bump when team grows past solo).
* Documented `bypass_actors: []` in Ruleset B as intentional.
* Added `text` language identifier to the directory-listing
fenced code block.
* Provided per-repo PATCH loop in §2 (org-bulk endpoint requires
uniform values; per-repo endpoint supports differing values).
* Corrected rollback command (PUT with partial body strips the
ruleset — use GET-jq-PUT).
Intentionally NOT changed in this commit:
- Ruleset A `required_approving_review_count: 0` — solo-dev intent,
documented in §3.
- CodeRabbit "repo doesn't exist" false positives on governance-
payloads.md — repos were verified when the schema was applied.
- Nitpick: rust-ci fmt job hardcoded `timeout-minutes: 5` — rustfmt
is too fast for this to matter; not worth the symmetry churn.
…check SC2086 flags '$FLAGS' as unquoted, but word-splitting is the point — the input is a cmake argument list. Add shellcheck disable=SC2086 so actionlint stops warning.
Fixes findings in the CodeRabbit review of de56ed8 (Actionable: 3, Nitpicks: 5). Critical — direct-invocation instead of sh -c (CodeRabbit): - rust-ci.yml: `sh -c "cargo clippy $FLAGS"` was a double-parse that reintroduced injection. Switched to multi-line `run: |` with direct `cargo clippy $FLAGS` and an inline `# shellcheck disable=SC2086` so bash word-splits FLAGS into argv without a shell re-parse. Same for `cargo test`. - dotnet-ci.yml: same pattern — `dotnet restore $SLN`, `dotnet build $SLN -c "$CONFIG" --no-restore`, `dotnet format $SLN ...`, `dotnet test $SLN -c "$CONFIG" ...`. All four steps use multi-line `run: |` + SC2086 disable. - python-ci.yml: ruff check/format, mypy, pytest all switched to direct invocation + SC2086 disable. Major — pre-flight CODEOWNERS claim was wrong (CodeRabbit): - ops/governance-payloads.md §pre-flight: previous version claimed all 14 repos had CODEOWNERS. Verified via gh api; three actually don't: resq-software/.github (fixed in this PR), resq-software/ardupilot (open), resq-software/resq-proto (fix pending in resq-proto#2). Warning block updated to list these explicitly with recommended actions before Ruleset A activation. New file — .github/CODEOWNERS: - Added default ownership for the resq-software/.github repo itself (was missing; CodeRabbit flagged). Points `*` at @WomB0ComB0 until a core team exists. Nitpicks — required.yml: - Added `harden-runner` (egress-policy: audit) to `validate-lang` and `required` jobs for consistency with the other jobs. - Fixed "typo defends" → "typo defenses" comment typo. Verified clean via actionlint. Deliberately not applied: - cpp-ci.yml: already uses direct invocation + SC2086 disable. - node-ci.yml: `sh -c "$CMD"` is semantically intentional — callers supply full command strings meant to be shell-parsed (e.g. `bun test --coverage && bun run typecheck`); CodeRabbit did not flag this in the 2nd round, implicitly accepting the trust model.
Addresses the two remaining CodeQL alerts on docker-publish.yml:
1. `actions/untrusted-checkout/medium` (line 90-95)
Checkout now passes `persist-credentials: false` — the git token
is not written into the working tree, so code checked out from
a caller-supplied ref cannot use it to push. The `inputs.ref`
override is preserved because callers legitimately build from
release tags; the persist-credentials:false mitigation keeps
that usage safe.
2. `actions/code-injection/medium` on ${{ inputs.context }}
All caller-controlled `with:` params on the build-push step are
routed through `env:` and referenced as `${{ env.X }}`. CodeQL
treats env as workflow-controlled, sanitizing the path from the
scanner's perspective. Applies to: BUILD_CONTEXT,
BUILD_DOCKERFILE, BUILD_PLATFORMS, BUILD_TAGS, BUILD_ARGS,
REGISTRY, IMAGE.
Not affected by CodeQL but also env-routed for consistency:
- docker/login-action registry
- actions/attest-build-provenance subject-name
The `inputs.push` + `inputs.attest` booleans are not routed
through env because CodeQL does not flag them (not a string-injection
vector) and `if:` expressions need the boolean value directly.
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: ResQ <engineer@resq.software>
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (6)
.github/workflows/cpp-ci.yml (1)
57-62: Harden-runner skipped on macOS/Windows; checkout persists credentials.Two small hardening gaps worth considering:
- Line 58:
if: runner.os == 'Linux'means macOS and Windows matrix legs skip step-security/harden-runner entirely. That's a known limitation of the action, so this is fine as-is, but worth documenting that egress auditing only covers the Linux leg.- Line 62:
actions/checkoutdefaults topersist-credentials: true, leaving aGITHUB_TOKENin the local git config for the rest of the job. Since no subsequent step needs git auth, settingpersist-credentials: falsereduces token exposure surface — consistent with the hardening stance noted elsewhere in this PR.🛡️ Proposed tweak
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/cpp-ci.yml around lines 57 - 62, The workflow currently only runs step-security/harden-runner on Linux (if: runner.os == 'Linux') which should be documented, and the subsequent actions/checkout step is leaving credentials persisted; update the actions/checkout invocation (the checkout step using actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd) to set persist-credentials: false to avoid leaving GITHUB_TOKEN in the local git config, and add a short comment near the Harden Runner step (or job-level comment) noting that egress-policy auditing applies only to the Linux matrix leg..github/workflows/required.yml (2)
119-127:securitywaits onvalidate-lang— intentional but costs ~5s on every run.Minor: making
securitydepend onvalidate-langmeans even valid-lang runs pay for a sequential harden-runner + case-statement step before security scanning starts. Since therequiredevaluator already checksVALIDATE_RESULTexplicitly, you could drop theneeds: [validate-lang]onsecurityto let it run in parallel with validation. Not critical — current ordering is defensible as "don't burn CodeQL minutes on obviously-bad input". Leaving as-is is fine.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/required.yml around lines 119 - 127, The security job currently declares a hard dependency needs: [validate-lang], causing it to wait ~5s unnecessarily; remove the needs: [validate-lang] line from the security job so it can run in parallel with validate-lang (the required evaluator already inspects VALIDATE_RESULT), leaving the existing uses: ./.github/workflows/security-scan.yml and inputs unchanged; reference the security job name "security", the validate job "validate-lang", and the evaluator variable "VALIDATE_RESULT" when making the change.
171-179: C++ dispatch forwards only 3 of cpp-ci.yml's 7 inputs.
cpp-ci.ymldeclaresbuild-dir,build-config,run-test, andtimeout-minutesin addition to the three forwarded here. Consumers callingrequired.ymlhave no way to override these (e.g.Releasebuilds, skipping tests, extending the 30-min timeout) without bypassing the aggregator. Other language jobs in this file (node, rust) expose more complete pass-throughs. For symmetry and to avoid follow-up PRs when a consumer needsReleaseor a longer timeout, consider surfacing them ascpp-*inputs onrequired.yml.♻️ Suggested additions
cpp-cmake-flags: type: string required: false default: "" + cpp-build-dir: + type: string + required: false + default: "build" + cpp-build-config: + type: string + required: false + default: "Debug" + cpp-run-test: + type: boolean + required: false + default: true + cpp-timeout-minutes: + type: number + required: false + default: 30 @@ cpp: needs: [validate-lang] if: ${{ inputs.lang == 'cpp' || inputs.lang == 'polyglot' }} name: C++ CI uses: ./.github/workflows/cpp-ci.yml with: os-list: ${{ inputs.cpp-os-list }} source-dir: ${{ inputs.cpp-source-dir }} cmake-flags: ${{ inputs.cpp-cmake-flags }} + build-dir: ${{ inputs.cpp-build-dir }} + build-config: ${{ inputs.cpp-build-config }} + run-test: ${{ inputs.cpp-run-test }} + timeout-minutes: ${{ inputs.cpp-timeout-minutes }}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/required.yml around lines 171 - 179, The cpp job in required.yml only forwards three inputs to the reusable workflow; add forwarding for the missing cpp-ci.yml inputs by mapping new wrapper inputs (e.g. cpp-build-dir, cpp-build-config, cpp-run-test, cpp-timeout-minutes) to the reusable workflow's expected keys (build-dir, build-config, run-test, timeout-minutes) in the cpp job's with block so callers of required.yml can override build dir, build configuration, test-running, and timeout without bypassing the aggregator; update the job's with to include lines like "build-dir: ${{ inputs.cpp-build-dir }}" etc. and ensure corresponding inputs are declared on required.yml (prefixed with cpp-).ops/governance-payloads.md (2)
84-92:while read -rloop is fine; minor robustness note.
jq -r '.repos[] |@JSON'emits one JSON object per line andread -rhandles it correctly. One caveat: if any repository_name ever contained whitespace or shell metacharacters, the quoted"/repos/resq-software/$repo/properties/values"expansion is safe, but therepo=$(jq -r …)extraction would still be. No change required — just confirmset -euo pipefailis used when this is pasted into an operator runbook so a mid-loop failure doesn't silently skip repos.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ops/governance-payloads.md` around lines 84 - 92, Ensure the snippet that iterates jq output with jq -r '.repos[] | `@json`' | while read -r row; do ... done runs under strict failure mode: add shell safety flags (set -euo pipefail) in the runbook or wrapper so any failure inside the while loop (e.g., gh api PATCH failures) aborts the script; keep the existing extraction (repo=$(jq -r '.repository_name' <<<"$row")) and props assignment (props=$(jq '{properties: .properties}' <<<"$row")) unchanged, just document or prepend the strict flags to guarantee a mid-loop error does not silently skip remaining repos.
44-51: Race condition in the fetch-mutate-PUT flow.The fetch→
jq→PUT pattern (here and in the rollback section, Lines 181-183) has no ETag/If-Match guard, so a concurrent edit in the UI or another script between the GET and PUT will be silently overwritten. For a governance workflow this is usually acceptable (single operator, rare changes), but worth calling out — or consider using the PATCH-style update if/when it supports partial ruleset bodies.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ops/governance-payloads.md` around lines 44 - 51, The fetch→jq→PUT pattern that updates the ruleset (the gh api GET -> jq '.enforcement = "active"' -> gh api --method PUT to /orgs/resq-software/rulesets/<id>) can cause a race where a concurrent edit is overwritten; change the flow to include optimistic concurrency using ETag/If-Match by first capturing the ETag from the GET response and passing it as an If-Match header on the PUT, or switch to a PATCH-style update when partial updates are supported (use the same /orgs/resq-software/rulesets/<id> endpoint with --method PATCH and a partial body) to avoid stomping concurrent edits..github/workflows/docker-publish.yml (1)
153-159: Configuration is correct — digest format and gate logic work as intended.The
docker/build-push-actionoutputs.digest is insha256:HEXformat, matching exactly whatactions/attest-build-provenance@v4.1.0requires for thesubject-digestinput. The condition${{ inputs.attest && inputs.push }}correctly prevents attestation whenpush: false.Per the official documentation,
actions/attest-build-provenanceis now a wrapper aroundactions/attest. While the current implementation is functional, consider usingactions/attestdirectly for new implementations going forward.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/docker-publish.yml around lines 153 - 159, Replace the attestation step that uses actions/attest-build-provenance@a2bbfa... with the upstream actions/attest action: update the step named "Attest build provenance" to call actions/attest (choose the current stable tag), keep the existing condition `${{ inputs.attest && inputs.push }}`, and pass the same inputs (`subject-name`, `subject-digest`, `push-to-registry`) so the behaviour remains identical while using the maintained upstream action.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/cpp-ci.yml:
- Around line 63-73: Replace the current unquoted $FLAGS expansion in the
"Configure" step with a safe array-expansion: accept the workflow input
"cmake-flags" as a JSON array, decode it into a bash array (e.g. FLAGS=($(jq -r
'.[]' <<< "$INPUT_CMAKE_FLAGS")) or similar) in the run block, then call cmake
with quoted array expansion cmake -B "$BUILD" "${FLAGS[@]}" "$SRC"; update the
env/input handling so the input is read from the "cmake-flags" input rather than
relying on shell-splitting, and add a brief header comment documenting that
consumers must pass a JSON array for cmake-flags (or, if you prefer minimal
change, instead add a comment above the Configure step explicitly stating that
cmake-flags is trusted input and must not be derived from untrusted PR context).
In @.github/workflows/docker-publish.yml:
- Around line 132-138: The login step hardcodes GHCR credentials (github.actor +
secrets.GITHUB_TOKEN) while registry is configurable, causing failures for
non-ghcr registries; update the workflow to accept caller-provided registry
credentials (e.g., define on.workflow_call.secrets like registry-username and
registry-password or inputs for them) and in the docker/login-action step use
those secrets when present, falling back to github.actor and
secrets.GITHUB_TOKEN only if registry-username/registry-password are unset;
alternatively add validation for the inputs.registry value to ensure it endsWith
"ghcr.io" and fail early if not—look for the "registry" input, the
docker/login-action step, and uses of "github.actor" / "secrets.GITHUB_TOKEN" to
implement this conditional credential selection or validation.
- Around line 120-130: The build-context regex is unsafe: the local-path branch
(used against BUILD_CONTEXT via ctx) currently allows “..” path traversal and
the unquoted inline regex contains stray single-quote characters that break
shell tokenization; fix by moving the full pattern into a variable (e.g.
pattern) and use [[ $ctx =~ $pattern ]] so the regex isn’t split by quotes,
replace the local-path alternative (currently \.?\/?[-._a-zA-Z0-9\/]+) with an
anchored segment-only form that disallows “..” (for example require one or more
path segments like ([A-Za-z0-9._-]+\/?)+) and ensure the overall pattern is
anchored with ^ and $; keep the remote branches but remove any literal single
quotes so the stored pattern is a valid POSIX ERE used via the pattern variable
when evaluating BUILD_CONTEXT and exporting SAFE_BUILD_CONTEXT.
In `@ops/governance-payloads.md`:
- Line 167: The verification command hardcodes "main" so it will fail if the
repo's default branch differs; update the check that runs gh api
/repos/resq-software/pypi/rules/branches/main to first query the repository
metadata (the repo's default_branch field via gh api /repos/:owner/:repo) and
then use that returned branch name when calling the rules/branches endpoint, or
explicitly document the assumption that the default branch must be "main" so
users know the limitation.
In `@ops/ruleset-b-critical.json`:
- Around line 18-24: Section 4 lacks an intent note explaining that
"required_approving_review_count": 0 is intentionally non-blocking (it requests
a code-owner review for visibility during solo-dev) while
"require_last_push_approval": true enforces blocking on subsequent pushes;
update the ruleset documentation or the JSON comment for Ruleset B to either (A)
add an explicit intent note in §4 stating that required_approving_review_count:
0 requests but does not block merges (mirroring §3’s intent), or (B) if you want
blocking behavior for the critical tier, change the parameter
"required_approving_review_count" from 0 to 1 in the parameters block so
code-owner review becomes a blocking requirement — reference the parameters
object and the keys required_approving_review_count and
require_last_push_approval when making the change.
---
Nitpick comments:
In @.github/workflows/cpp-ci.yml:
- Around line 57-62: The workflow currently only runs
step-security/harden-runner on Linux (if: runner.os == 'Linux') which should be
documented, and the subsequent actions/checkout step is leaving credentials
persisted; update the actions/checkout invocation (the checkout step using
actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd) to set
persist-credentials: false to avoid leaving GITHUB_TOKEN in the local git
config, and add a short comment near the Harden Runner step (or job-level
comment) noting that egress-policy auditing applies only to the Linux matrix
leg.
In @.github/workflows/docker-publish.yml:
- Around line 153-159: Replace the attestation step that uses
actions/attest-build-provenance@a2bbfa... with the upstream actions/attest
action: update the step named "Attest build provenance" to call actions/attest
(choose the current stable tag), keep the existing condition `${{ inputs.attest
&& inputs.push }}`, and pass the same inputs (`subject-name`, `subject-digest`,
`push-to-registry`) so the behaviour remains identical while using the
maintained upstream action.
In @.github/workflows/required.yml:
- Around line 119-127: The security job currently declares a hard dependency
needs: [validate-lang], causing it to wait ~5s unnecessarily; remove the needs:
[validate-lang] line from the security job so it can run in parallel with
validate-lang (the required evaluator already inspects VALIDATE_RESULT), leaving
the existing uses: ./.github/workflows/security-scan.yml and inputs unchanged;
reference the security job name "security", the validate job "validate-lang",
and the evaluator variable "VALIDATE_RESULT" when making the change.
- Around line 171-179: The cpp job in required.yml only forwards three inputs to
the reusable workflow; add forwarding for the missing cpp-ci.yml inputs by
mapping new wrapper inputs (e.g. cpp-build-dir, cpp-build-config, cpp-run-test,
cpp-timeout-minutes) to the reusable workflow's expected keys (build-dir,
build-config, run-test, timeout-minutes) in the cpp job's with block so callers
of required.yml can override build dir, build configuration, test-running, and
timeout without bypassing the aggregator; update the job's with to include lines
like "build-dir: ${{ inputs.cpp-build-dir }}" etc. and ensure corresponding
inputs are declared on required.yml (prefixed with cpp-).
In `@ops/governance-payloads.md`:
- Around line 84-92: Ensure the snippet that iterates jq output with jq -r
'.repos[] | `@json`' | while read -r row; do ... done runs under strict failure
mode: add shell safety flags (set -euo pipefail) in the runbook or wrapper so
any failure inside the while loop (e.g., gh api PATCH failures) aborts the
script; keep the existing extraction (repo=$(jq -r '.repository_name'
<<<"$row")) and props assignment (props=$(jq '{properties: .properties}'
<<<"$row")) unchanged, just document or prepend the strict flags to guarantee a
mid-loop error does not silently skip remaining repos.
- Around line 44-51: The fetch→jq→PUT pattern that updates the ruleset (the gh
api GET -> jq '.enforcement = "active"' -> gh api --method PUT to
/orgs/resq-software/rulesets/<id>) can cause a race where a concurrent edit is
overwritten; change the flow to include optimistic concurrency using
ETag/If-Match by first capturing the ETag from the GET response and passing it
as an If-Match header on the PUT, or switch to a PATCH-style update when partial
updates are supported (use the same /orgs/resq-software/rulesets/<id> endpoint
with --method PATCH and a partial body) to avoid stomping concurrent edits.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d6c53ff5-6dbc-4de8-a93f-689b18be1c2e
📒 Files selected for processing (12)
.github/CODEOWNERS.github/workflows/cpp-ci.yml.github/workflows/docker-publish.yml.github/workflows/dotnet-ci.yml.github/workflows/python-ci.yml.github/workflows/required.yml.github/workflows/rust-ci.ymlops/governance-payloads.mdops/properties-schema.jsonops/properties-values.jsonops/ruleset-a-baseline.jsonops/ruleset-b-critical.json
✅ Files skipped from review due to trivial changes (5)
- ops/properties-values.json
- ops/properties-schema.json
- ops/ruleset-a-baseline.json
- .github/CODEOWNERS
- .github/workflows/rust-ci.yml
🚧 Files skipped from review as they are similar to previous changes (2)
- .github/workflows/dotnet-ci.yml
- .github/workflows/python-ci.yml
| - name: Configure | ||
| shell: bash | ||
| env: | ||
| SRC: ${{ inputs.source-dir }} | ||
| BUILD: ${{ inputs.build-dir }} | ||
| FLAGS: ${{ inputs.cmake-flags }} | ||
| # shellcheck disable=SC2086 - FLAGS is intentionally word-split into | ||
| # multiple cmake arguments; quoting would pass them as a single token. | ||
| run: | | ||
| # shellcheck disable=SC2086 | ||
| cmake -B "$BUILD" $FLAGS "$SRC" |
There was a problem hiding this comment.
$FLAGS word-splitting still allows caller-controlled argument injection into cmake.
Routing cmake-flags through env: prevents shell injection into the YAML-generated script (addressing the CodeQL finding), but $FLAGS is intentionally left unquoted so a caller can pass multiple flags. That means a caller can still inject arbitrary cmake arguments — e.g. -DCMAKE_CXX_COMPILER_LAUNCHER=..., -P <script>, or extra ;-separated generator expressions — which on a reusable workflow invoked from a consumer repo is effectively arbitrary code execution on the runner via the build step.
Since this is a reusable workflow consumed across the org, the trust boundary is "whatever the calling repo's workflow passes in", which for PRs from forks can be attacker-controlled. Consider one of:
- Documenting that
cmake-flagsis trusted input and must not be derived from PR-event context in consumers. - Accepting
cmake-flagsas a JSON array and expanding viajq -r '.[]'into abasharray, preserving per-arg quoting:
♻️ Array-based expansion
- - name: Configure
- shell: bash
- env:
- SRC: ${{ inputs.source-dir }}
- BUILD: ${{ inputs.build-dir }}
- FLAGS: ${{ inputs.cmake-flags }}
- # shellcheck disable=SC2086 - FLAGS is intentionally word-split into
- # multiple cmake arguments; quoting would pass them as a single token.
- run: |
- # shellcheck disable=SC2086
- cmake -B "$BUILD" $FLAGS "$SRC"
+ - name: Configure
+ shell: bash
+ env:
+ SRC: ${{ inputs.source-dir }}
+ BUILD: ${{ inputs.build-dir }}
+ FLAGS_JSON: ${{ inputs.cmake-flags-json }} # e.g. '["-DX=1","-DY=2"]'
+ run: |
+ set -eu
+ mapfile -t FLAGS < <(printf '%s' "$FLAGS_JSON" | jq -r '.[]')
+ cmake -B "$BUILD" "${FLAGS[@]}" "$SRC"At minimum, add a comment to the workflow header making the trust assumption on cmake-flags explicit.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/cpp-ci.yml around lines 63 - 73, Replace the current
unquoted $FLAGS expansion in the "Configure" step with a safe array-expansion:
accept the workflow input "cmake-flags" as a JSON array, decode it into a bash
array (e.g. FLAGS=($(jq -r '.[]' <<< "$INPUT_CMAKE_FLAGS")) or similar) in the
run block, then call cmake with quoted array expansion cmake -B "$BUILD"
"${FLAGS[@]}" "$SRC"; update the env/input handling so the input is read from
the "cmake-flags" input rather than relying on shell-splitting, and add a brief
header comment documenting that consumers must pass a JSON array for cmake-flags
(or, if you prefer minimal change, instead add a comment above the Configure
step explicitly stating that cmake-flags is trusted input and must not be
derived from untrusted PR context).
| - if: ${{ inputs.push }} | ||
| name: Log in to registry | ||
| uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 | ||
| with: | ||
| registry: ${{ env.REGISTRY }} | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} |
There was a problem hiding this comment.
Login credentials are hardcoded to the GHCR flow but registry is a configurable input.
registry defaults to ghcr.io but is a caller-controlled input, while the login step always uses github.actor + secrets.GITHUB_TOKEN. Those credentials only authenticate against GHCR, so a caller that sets registry: docker.io (or an ECR/ACR URL) will fail at this step with no clear signal that this workflow is GHCR-only.
Either (a) document this workflow as GHCR-only and validate inputs.registry ends with ghcr.io, or (b) expose caller secrets via on.workflow_call.secrets (e.g., registry-username, registry-password) and fall back to the GHCR defaults when unset.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/docker-publish.yml around lines 132 - 138, The login step
hardcodes GHCR credentials (github.actor + secrets.GITHUB_TOKEN) while registry
is configurable, causing failures for non-ghcr registries; update the workflow
to accept caller-provided registry credentials (e.g., define
on.workflow_call.secrets like registry-username and registry-password or inputs
for them) and in the docker/login-action step use those secrets when present,
falling back to github.actor and secrets.GITHUB_TOKEN only if
registry-username/registry-password are unset; alternatively add validation for
the inputs.registry value to ensure it endsWith "ghcr.io" and fail early if
not—look for the "registry" input, the docker/login-action step, and uses of
"github.actor" / "secrets.GITHUB_TOKEN" to implement this conditional credential
selection or validation.
| gh api /orgs/resq-software/properties/schema | ||
| gh api /orgs/resq-software/properties/values | ||
| gh api /orgs/resq-software/rulesets | ||
| gh api /repos/resq-software/pypi/rules/branches/main |
There was a problem hiding this comment.
Verification command hardcodes main as the default branch.
gh api /repos/resq-software/pypi/rules/branches/main will return rules only if pypi's default branch is literally main. If it's master/trunk/etc., this will 404 and mislead whoever runs the check. Either resolve the default branch dynamically or note the assumption.
Proposed tweak
-gh api /repos/resq-software/pypi/rules/branches/main
+# Resolve default branch dynamically so this works regardless of repo convention.
+default=$(gh api /repos/resq-software/pypi --jq .default_branch)
+gh api "/repos/resq-software/pypi/rules/branches/$default"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| gh api /repos/resq-software/pypi/rules/branches/main | |
| # Resolve default branch dynamically so this works regardless of repo convention. | |
| default=$(gh api /repos/resq-software/pypi --jq .default_branch) | |
| gh api "/repos/resq-software/pypi/rules/branches/$default" |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ops/governance-payloads.md` at line 167, The verification command hardcodes
"main" so it will fail if the repo's default branch differs; update the check
that runs gh api /repos/resq-software/pypi/rules/branches/main to first query
the repository metadata (the repo's default_branch field via gh api
/repos/:owner/:repo) and then use that returned branch name when calling the
rules/branches endpoint, or explicitly document the assumption that the default
branch must be "main" so users know the limitation.
| "parameters": { | ||
| "required_approving_review_count": 0, | ||
| "dismiss_stale_reviews_on_push": true, | ||
| "require_code_owner_review": true, | ||
| "require_last_push_approval": true, | ||
| "required_review_thread_resolution": true | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
GitHub ruleset pull_request required_approving_review_count 0 require_code_owner_review behavior
💡 Result:
In GitHub rulesets, for a pull_request rule with required_approving_review_count set to 0, no general approving reviews are required to merge the pull request. This behaves the same as in classic branch protection rules, where setting the number of required approving reviews to 0 means reviewers are not required. However, if require_code_owner_review is set to true in the same pull_request rule, an approving review from a code owner is still required for pull requests that modify files with designated code owners in the CODEOWNERS file. The require_code_owner_review is an independent boolean requirement that enforces code owner approval regardless of the required_approving_review_count value. This is confirmed in the GitHub REST API documentation for rulesets, which defines both parameters in the pull_request rule object: - required_approving_review_count (integer, required): The number of approving reviews that are required before a pull request can be merged. - require_code_owner_review (boolean, required): Require an approving review in pull requests that modify files that have a designated code owner. Official docs note that setting required_approving_review_count to 0 disables the general review requirement, but code owner reviews are separately enforced if enabled. Rulesets layer with branch protection rules, taking the most restrictive setting.
Citations:
- 1: https://docs.github.com/en/rest/branches/branch-protection?apiVersion=2026-03-10
- 2: https://docs.github.com/rest/branches/branch-protection
- 3: https://docs.github.com/en/rest/repos/rules
- 4: https://docs.github.com/rest/repos/rules
🏁 Script executed:
# Check if the files exist and read their content
git ls-files | grep -E "(ruleset|governance)" | head -20Repository: resq-software/.github
Length of output: 147
🏁 Script executed:
# Read the ruleset-b-critical.json file
cat ops/ruleset-b-critical.json 2>/dev/null || echo "File not found"Repository: resq-software/.github
Length of output: 787
🏁 Script executed:
# Read the governance documentation
cat ops/governance-payloads.md 2>/dev/null || head -100 ops/governance-payloads.md 2>/dev/null || echo "File not found"Repository: resq-software/.github
Length of output: 6701
🌐 Web query:
GitHub ruleset require_last_push_approval required_approving_review_count interaction
💡 Result:
In GitHub repository rulesets, the 'pull_request' rule enforces that changes to protected branches must go through a pull request before merging. Within its parameters: - required_approving_review_count (integer, 0-10, required): Specifies the total number of approving reviews needed before a PR can merge. - require_last_push_approval (boolean, required): When true, requires that the most recent reviewable push to the PR branch be approved by someone other than the person who made that push. This ensures an additional approval from a different reviewer after the latest changes, even if the total count is met. It works alongside required_approving_review_count; the PR needs both the total approvals and this specific non-self-approval for the last push. These are independent but complementary: the count sets the minimum approvals overall, while require_last_push_approval adds a "fresh eyes" check on final changes. Both are part of the same pull_request rule parameters in ruleset creation/update via REST API or UI under "Require a pull request before merging" additional settings. Examples from GitHub's safe-settings repo show them used together: required_approving_review_count: 1 and require_last_push_approval: true. Note: These replace legacy branch protection rules with more flexible rulesets, supporting layering and bypass actors.
Citations:
- 1: https://docs.github.com/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets
- 2: https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets
- 3: https://docs.github.com/en/rest/repos/rules
- 4: https://github.com/github/safe-settings/blob/main-enterprise/docs/sample-settings/settings.yml
- 5: https://docs.github.com/rest/repos/rules
- 6: https://docs.github.com/en/rest/repos/rules?apiVersion=2026-03-10
Add intent note to §4 explaining required_approving_review_count: 0.
Section 4 describes require_last_push_approval: true ("any new push to a PR after approval re-requires approval") but does not explain required_approving_review_count: 0, which appears identically in both Ruleset A and Ruleset B. Per §3's intent note, this configuration means code-owner review is requested but non-blocking — a visibility guarantee for the solo-dev stage. Either clarify that this same intent applies to Ruleset B, or if critical tier should enforce stricter approval gates, bump required_approving_review_count to 1 so code-owner review actually blocks merge.
Note: require_last_push_approval does enforce blocking behavior (latest push must be approved by someone other than the pusher), so Ruleset B is not as permissive as it might initially appear.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ops/ruleset-b-critical.json` around lines 18 - 24, Section 4 lacks an intent
note explaining that "required_approving_review_count": 0 is intentionally
non-blocking (it requests a code-owner review for visibility during solo-dev)
while "require_last_push_approval": true enforces blocking on subsequent pushes;
update the ruleset documentation or the JSON comment for Ruleset B to either (A)
add an explicit intent note in §4 stating that required_approving_review_count:
0 requests but does not block merges (mirroring §3’s intent), or (B) if you want
blocking behavior for the critical tier, change the parameter
"required_approving_review_count" from 0 to 1 in the parameters block so
code-owner review becomes a blocking requirement — reference the parameters
object and the keys required_approving_review_count and
require_last_push_approval when making the change.
…ws' into auto-commit-20260417-060430 # Conflicts: # .github/workflows/docker-publish.yml
There was a problem hiding this comment.
♻️ Duplicate comments (1)
.github/workflows/docker-publish.yml (1)
33-37:⚠️ Potential issue | 🟠 MajorRegistry is configurable, but auth is GHCR-specific.
Line 33-37 lets callers set any registry, but Line 182-188 always authenticates with
github.actor+secrets.GITHUB_TOKEN(GHCR-oriented). Non-GHCR pushes will fail unexpectedly.Suggested fix (support custom registry credentials with GHCR fallback)
on: workflow_call: inputs: @@ registry: type: string required: false default: "ghcr.io" + secrets: + registry-username: + required: false + registry-password: + required: false @@ - if: ${{ inputs.push }} name: Log in to registry uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ${{ env.SAFE_REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + username: ${{ secrets.registry-username || github.actor }} + password: ${{ secrets.registry-password || secrets.GITHUB_TOKEN }}Also applies to: 182-188
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/docker-publish.yml around lines 33 - 37, The workflow currently accepts an input named "registry" but always authenticates using github.actor and secrets.GITHUB_TOKEN (GHCR-specific); update the workflow inputs to add optional "registry_username" and "registry_password" (or "registry_token") and modify the registry login step (the docker login / docker/login-action step) to use those credentials when provided, falling back to github.actor + secrets.GITHUB_TOKEN only if no custom credentials are supplied; ensure the login action uses the "registry" input value as the registry host and uses the provided username/password variables for the action's with: fields.
🧹 Nitpick comments (1)
.github/workflows/docker-publish.yml (1)
89-94: Permissions are broader than needed for non-push/non-attest runs.Line 89-94 always grants write scopes, even when
push: falseand/orattest: false. Consider splitting build/publish/attest into separate jobs so write permissions are only granted where required.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/docker-publish.yml around lines 89 - 94, The workflow's permissions block currently grants write scopes unconditionally (permissions: contents: read, packages: write, id-token: write, attestations: write), exposing broader access for runs that don't need it; change the workflow to split responsibilities into separate jobs (e.g., build, publish, attest) and scope permissions per job so only the publish job gets packages: write and id-token: write and only the attest job gets attestations: write, while the build job keeps minimal permissions (contents: read) and outputs remain unchanged—update the jobs and their permissions entries accordingly so push:false or attest:false runs do not receive write permissions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In @.github/workflows/docker-publish.yml:
- Around line 33-37: The workflow currently accepts an input named "registry"
but always authenticates using github.actor and secrets.GITHUB_TOKEN
(GHCR-specific); update the workflow inputs to add optional "registry_username"
and "registry_password" (or "registry_token") and modify the registry login step
(the docker login / docker/login-action step) to use those credentials when
provided, falling back to github.actor + secrets.GITHUB_TOKEN only if no custom
credentials are supplied; ensure the login action uses the "registry" input
value as the registry host and uses the provided username/password variables for
the action's with: fields.
---
Nitpick comments:
In @.github/workflows/docker-publish.yml:
- Around line 89-94: The workflow's permissions block currently grants write
scopes unconditionally (permissions: contents: read, packages: write, id-token:
write, attestations: write), exposing broader access for runs that don't need
it; change the workflow to split responsibilities into separate jobs (e.g.,
build, publish, attest) and scope permissions per job so only the publish job
gets packages: write and id-token: write and only the attest job gets
attestations: write, while the build job keeps minimal permissions (contents:
read) and outputs remain unchanged—update the jobs and their permissions entries
accordingly so push:false or attest:false runs do not receive write permissions.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 111b5581-ad50-4489-b0bb-41140e7f2c05
📒 Files selected for processing (1)
.github/workflows/docker-publish.yml
Updates @sha from the feat-branch tip to the merge commit of resq-software/.github#12 (f4b51a620aa1bf89c0bce4f434b36f92ff7d517d). Functionally equivalent — same content — but pins to a ref that now exists on main rather than a closed PR branch.
Updates @sha from the feat-branch tip to the merge commit of resq-software/.github#12 (f4b51a620aa1bf89c0bce4f434b36f92ff7d517d). Functionally equivalent — same content — but pins to a ref that now exists on main rather than a closed PR branch.
Updates @sha from the feat-branch tip to the merge commit of resq-software/.github#12 (f4b51a620aa1bf89c0bce4f434b36f92ff7d517d). Functionally equivalent — same content — but pins to a ref that now exists on main rather than a closed PR branch.
Updates @sha from the feat-branch tip to the merge commit of resq-software/.github#12 (f4b51a620aa1bf89c0bce4f434b36f92ff7d517d). Functionally equivalent — same content — but pins to a ref that now exists on main rather than a closed PR branch.
Updates @sha from the feat-branch tip to the merge commit of resq-software/.github#12 (f4b51a620aa1bf89c0bce4f434b36f92ff7d517d). Functionally equivalent — same content — but pins to a ref that now exists on main rather than a closed PR branch.
Updates @sha from the feat-branch tip to the merge commit of resq-software/.github#12 (f4b51a620aa1bf89c0bce4f434b36f92ff7d517d). Functionally equivalent — same content — but pins to a ref that now exists on main rather than a closed PR branch.
* ci: add placeholder `required` status gate for org ruleset Satisfies the `required` status-check context in org ruleset `default-branch-baseline` (id 15191038, currently evaluate mode). Placeholder: this job always passes. Before the ruleset flips to active, harden it with `needs:` on real language-CI jobs or replace with a call to a reusable workflow from resq-software/.github once the matching lang-ci.yml exists. * ci: harden `required` gate — call real dotnet-ci reusable Replaces the always-pass placeholder with a ci.yml that calls the org-wide `required` aggregator with `lang: dotnet` and the correct solution path. Pinned to resq-software/.github@6410acb (the commit introducing dotnet-ci.yml). After PR#12 on resq-software/.github merges, re-pin to the merge commit SHA or a semver tag. The top-level `required` job emits the status-check context consumed by org ruleset `default-branch-baseline`. * ci: re-pin reusable workflow ref to resq-software/.github main Updates @sha from the feat-branch tip to the merge commit of resq-software/.github#12 (f4b51a620aa1bf89c0bce4f434b36f92ff7d517d). Functionally equivalent — same content — but pins to a ref that now exists on main rather than a closed PR branch.
* ci: add placeholder `required` status gate for org ruleset Satisfies the `required` status-check context in org ruleset `default-branch-baseline` (id 15191038, currently evaluate mode). Placeholder: this job always passes. Before the ruleset flips to active, harden it with `needs:` on real language-CI jobs or replace with a call to a reusable workflow from resq-software/.github once the matching lang-ci.yml exists. * ci: harden `required` gate — call real dotnet-ci reusable Replaces the always-pass placeholder with a ci.yml that calls the org-wide `required` aggregator with `lang: dotnet` and the correct solution path. Pinned to resq-software/.github@6410acb (the commit introducing dotnet-ci.yml). After PR#12 on resq-software/.github merges, re-pin to the merge commit SHA or a semver tag. The top-level `required` job emits the status-check context consumed by org ruleset `default-branch-baseline`. * ci: re-pin reusable workflow ref to resq-software/.github main Updates @sha from the feat-branch tip to the merge commit of resq-software/.github#12 (f4b51a620aa1bf89c0bce4f434b36f92ff7d517d). Functionally equivalent — same content — but pins to a ref that now exists on main rather than a closed PR branch.
* ci: add reusable rust-ci from resq-software/.github First CI workflow for this repo. Calls the org-wide reusable `rust-ci.yml` (fmt + clippy + test) pinned to the PR#12 tip SHA. Adds a top-level `required` aggregator job to emit the status-check context consumed by the org ruleset `default-branch-baseline` (currently evaluate-mode). Security scanning remains in security.yml — independent cadence. After resq-software/.github#12 lands, update the @sha ref. * ci: re-pin reusable workflow ref to resq-software/.github main Updates @sha from the feat-branch tip to the merge commit of resq-software/.github#12 (f4b51a620aa1bf89c0bce4f434b36f92ff7d517d). Functionally equivalent — same content — but pins to a ref that now exists on main rather than a closed PR branch.
* ci: add placeholder `required` status gate alongside existing CI Satisfies the `required` status-check context in org ruleset `default-branch-baseline` (id 15191038, evaluate mode). Placeholder that always passes; this repo'\''s existing CI workflows continue to run independently. Before the ruleset flips to active, harden this job with `needs:` on the existing CI jobs — or swap for a call to a reusable workflow in resq-software/.github. * ci: harden to cpp-ci reusable; remove inline ci.yml + placeholder Replaces the previous inline cmake/ctest matrix and the placeholder required.yml with a single ci.yml that calls the org-wide `required` aggregator with `lang: cpp` and the correct source-dir + cmake-flags preserved from the prior workflow. Pinned to resq-software/.github@6410acb (the commit introducing cpp-ci.yml). Re-pin to the merge commit or tag after PR#12 lands. Parity: - os matrix: ubuntu + macos + windows (same as before) - source: packages/resq-common (same) - flag: -DRESQ_COMMON_BUILD_TESTS=ON (same) Additionally runs CodeQL for c-cpp and the full security-scan suite via the reusable — upgrade from the prior CI. * ci: re-pin reusable workflow ref to resq-software/.github main Updates @sha from the feat-branch tip to the merge commit of resq-software/.github#12 (f4b51a620aa1bf89c0bce4f434b36f92ff7d517d). Functionally equivalent — same content — but pins to a ref that now exists on main rather than a closed PR branch.
* ci: migrate to reusable node-ci in resq-software/.github Replace inline build/test/lint jobs with a thin caller of `resq-software/.github/.github/workflows/node-ci.yml` pinned to the PR#12 tip SHA (5a72372). Adds a top-level `required` aggregator job to emit the status-check context consumed by the org ruleset `default-branch-baseline` (currently in evaluate mode). Behavior parity: - lint: bun run lint (biome) - typecheck: bun run tsc --noEmit - build: bun run build - test: bun test --coverage Security scanning remains in security.yml (independent cadence). When PR#12 lands on resq-software/.github, update the @sha ref to pin against the merge commit or a semver tag. * ci: re-pin reusable workflow ref to resq-software/.github main Updates @sha from the feat-branch tip to the merge commit of resq-software/.github#12 (f4b51a620aa1bf89c0bce4f434b36f92ff7d517d). Functionally equivalent — same content — but pins to a ref that now exists on main rather than a closed PR branch. * chore(deps): regenerate bun.lock to match package.json Previous lockfile drifted from package.json, causing 'bun install --frozen-lockfile' in CI to fail with: error: lockfile had changes, but lockfile is frozen Regenerated locally; no dependency version changes required beyond what package.json already specified.
* ci: migrate to reusable rust-ci in resq-software/.github Replaces inline test/fmt jobs in ci.yml with a thin caller of the org-wide reusable workflow. Subsumes clippy.yml and deny.yml (the reusable wraps fmt + clippy + test + coverage + cargo-deny into one dispatch). Adds a top-level `required` aggregator job that emits the status-check context consumed by the org ruleset `default-branch-baseline` (id 15191038, currently evaluate mode). Pinned to the PR#12 tip SHA; re-pin after that PR lands. Parity: - fmt: cargo fmt --all --check - clippy: cargo clippy --all-targets --all-features -- -D warnings - test: cargo llvm-cov --workspace --lcov (codecov upload) - deny: cargo-deny check --all-features (via deny.toml) Security scanning remains in security.yml — independent cadence. * ci: re-pin reusable workflow ref to resq-software/.github main Updates @sha from the feat-branch tip to the merge commit of resq-software/.github#12 (f4b51a620aa1bf89c0bce4f434b36f92ff7d517d). Functionally equivalent — same content — but pins to a ref that now exists on main rather than a closed PR branch. * fix(ci): resolve clippy explicit_counter_loop + cargo-deny failures The migration to reusable rust-ci.yml surfaced two pre-existing issues that the old per-workflow CI didn't catch: 1. clippy::explicit_counter_loop in resq-dsa::rabin_karp Replaced `let mut i = 1; for c in chars { …; i += 1; }` with `for (i, c) in (1..).zip(chars) { … }`. Idiomatic, avoids the lint introduced by Rust 1.95, behavior unchanged. 2. cargo-deny failures — three licenses + one advisory: - RUSTSEC-2026-0099 (rustls-webpki name-constraint bug) → `cargo update -p rustls-webpki` bumps 0.103.10 → 0.103.12 (Cargo.lock delta only). - CDDL-1.0 (inferno, via resq-flame profiling stack) added to licenses.allow with rationale. - CDLA-Permissive-2.0 (webpki-root-certs root cert DB) added to licenses.allow. - MPL-2.0 (colored) added as a crate-specific exception rather than widening the org-wide MPL allowance. Verified locally: `cargo deny --all-features check` → all four sections (advisories, bans, licenses, sources) pass. * fix(clippy): address Rust 1.95 warnings surfaced by reusable rust-ci CI on the migrated rust-ci.yml (which now runs clippy across the whole workspace) surfaced four pedantic/default warnings that were elevated to errors by -D warnings: 1. resq-clean src/main.rs:100 map_unwrap_or -> fs::metadata(&path).map_or(0, |m| m.len()) 2. resq-clean src/main.rs:110 unnecessary_sort_by -> entries.sort_by_key(|e| std::cmp::Reverse(e.size)) 3. resq-cli src/commands/pre_commit.rs:1043/1048 collapsible_match -> KeyCode::<X> if <cond> => { <body> } 4. resq-cli tests/format.rs:19 map_unwrap_or -> Command::new("which").arg(cmd).output() .is_ok_and(|o| o.status.success()) All four are semantically-identical refactors; no behavior change. Verified clean locally against rustc 1.95.0: cargo +stable clippy --all-targets --all-features -- -D warnings * ci: re-pin reusable rust-ci to .github main (post-#13 merge) Picks up the taiki-e SHA correction and the Docker-bound cargo-deny replacement (dtolnay/rust-toolchain + taiki-e install cargo-deny + direct cargo deny check) so the run respects this repo's rust-toolchain.toml musl pin. Updates @f4b51a6 → @2e2af3e5e32b646480a1cb1b41875e5073020814.
* ci: migrate to reusable rust-ci in resq-software/.github Replaces inline test/fmt jobs in ci.yml with a thin caller of the org-wide reusable workflow. Subsumes clippy.yml and deny.yml (the reusable wraps fmt + clippy + test + coverage + cargo-deny into one dispatch). Adds a top-level `required` aggregator job that emits the status-check context consumed by the org ruleset `default-branch-baseline` (id 15191038, currently evaluate mode). Pinned to the PR#12 tip SHA; re-pin after that PR lands. Parity: - fmt: cargo fmt --all --check - clippy: cargo clippy --all-targets --all-features -- -D warnings - test: cargo llvm-cov --workspace --lcov (codecov upload) - deny: cargo-deny check --all-features (via deny.toml) Security scanning remains in security.yml — independent cadence. * ci: re-pin reusable workflow ref to resq-software/.github main Updates @sha from the feat-branch tip to the merge commit of resq-software/.github#12 (f4b51a620aa1bf89c0bce4f434b36f92ff7d517d). Functionally equivalent — same content — but pins to a ref that now exists on main rather than a closed PR branch. * fix(ci): resolve clippy explicit_counter_loop + cargo-deny failures The migration to reusable rust-ci.yml surfaced two pre-existing issues that the old per-workflow CI didn't catch: 1. clippy::explicit_counter_loop in resq-dsa::rabin_karp Replaced `let mut i = 1; for c in chars { …; i += 1; }` with `for (i, c) in (1..).zip(chars) { … }`. Idiomatic, avoids the lint introduced by Rust 1.95, behavior unchanged. 2. cargo-deny failures — three licenses + one advisory: - RUSTSEC-2026-0099 (rustls-webpki name-constraint bug) → `cargo update -p rustls-webpki` bumps 0.103.10 → 0.103.12 (Cargo.lock delta only). - CDDL-1.0 (inferno, via resq-flame profiling stack) added to licenses.allow with rationale. - CDLA-Permissive-2.0 (webpki-root-certs root cert DB) added to licenses.allow. - MPL-2.0 (colored) added as a crate-specific exception rather than widening the org-wide MPL allowance. Verified locally: `cargo deny --all-features check` → all four sections (advisories, bans, licenses, sources) pass. * fix(clippy): address Rust 1.95 warnings surfaced by reusable rust-ci CI on the migrated rust-ci.yml (which now runs clippy across the whole workspace) surfaced four pedantic/default warnings that were elevated to errors by -D warnings: 1. resq-clean src/main.rs:100 map_unwrap_or -> fs::metadata(&path).map_or(0, |m| m.len()) 2. resq-clean src/main.rs:110 unnecessary_sort_by -> entries.sort_by_key(|e| std::cmp::Reverse(e.size)) 3. resq-cli src/commands/pre_commit.rs:1043/1048 collapsible_match -> KeyCode::<X> if <cond> => { <body> } 4. resq-cli tests/format.rs:19 map_unwrap_or -> Command::new("which").arg(cmd).output() .is_ok_and(|o| o.status.success()) All four are semantically-identical refactors; no behavior change. Verified clean locally against rustc 1.95.0: cargo +stable clippy --all-targets --all-features -- -D warnings * ci: re-pin reusable rust-ci to .github main (post-#13 merge) Picks up the taiki-e SHA correction and the Docker-bound cargo-deny replacement (dtolnay/rust-toolchain + taiki-e install cargo-deny + direct cargo deny check) so the run respects this repo's rust-toolchain.toml musl pin. Updates @f4b51a6 → @2e2af3e5e32b646480a1cb1b41875e5073020814.
#70) * ci: wire org-wide security-scan + dependabot + CODEOWNERS (#63) Standardization pass across public ResQ repos. Inherits org-level defaults (CoC / Contributing / Security / Support / PR + issue templates) from resq-software/.github. .github/workflows/security.yml Thin caller for the reusable workflow with languages=["actions"]. .github/CODEOWNERS Default owner. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore(ci): SHA-pin third-party GitHub Actions refs (#64) Pins every external `uses:` ref to a commit SHA with a trailing `# <tag>` comment. Closes the tj-actions-class supply-chain attack vector where a malicious tag push exfiltrates secrets. Dependabot continues to update via the version comment. Action refs that can't be pinned (e.g. `dtolnay/rust-toolchain@stable` which is intentionally a moving ref) are left as-is — these are floating channel refs, not tag refs. * ci(security): enable Semgrep SAST (#65) * fix(ci): quiet actionlint SC2035 by using -- glob separator (#66) * fix(deny): migrate deny.toml to cargo-deny v2 schema (#67) cargo-deny v0.14+ removed [licenses].unlicensed and [licenses].deny. Schema now uses [licenses].version = 2 + implicit-denial-by-omission from allow list. See EmbarkStudios/cargo-deny#611. * chore(nix): bump nixpkgs 24.11 → 25.11 + refresh lock (#68) * chore(bootstrap): retire duplicate setup scripts, delegate to resq-software/dev Replace bootstrap.sh with a ~9-line thin wrapper that curl-pipes the canonical dev/install.sh with REPO=crates. Delete the per-repo scripts/setup.sh and scripts/lib/shell-utils.sh drifted copies. Single source of truth lives in resq-software/dev — no more per-repo drift, and CI surface shrinks by one directory. * chore(resq-ai): scaffold new crate with workspace integration * feat(resq-ai): add token estimation and line-boundary truncation * feat(resq-ai): add config cascade and provider enum * feat(resq-ai): add Anthropic, OpenAI, Gemini providers with dispatch * feat(resq-cli): add resq commit subcommand with AI-powered message generation * fix: resolve CI failures and address review feedback for resq-ai PR - Fix clippy: use enumerate-zip pattern in trie.rs, div_ceil in token.rs - Fix rustfmt: run cargo fmt on config.rs, commit.rs, gemini.rs - Fix test race: add Mutex guard to serialize env-mutating config tests - Fix cargo-deny: allow MPL-2.0, CDDL-1.0, CDLA-Permissive-2.0 licenses - Fix gitleaks: replace suspicious test API key with benign placeholder - Fix clippy doc_markdown lint for OpenAI in lib.rs doc comment - Fix bootstrap.sh: validate curl download, forward $@ to installer - Fix token.rs: ensure UTF-8 char boundary before byte slicing - Fix anthropic.rs: make ContentBlock.text optional for non-text blocks - Fix gemini.rs: use x-goog-api-key header instead of query param - Fix config.rs: error on unknown RESQ_AI_PROVIDER instead of silently falling back - Fix commit.rs: RAII guard for terminal raw mode, TTY check before interactive selection - Add commit command to module doc list in mod.rs * fix(resq-ai): use proper URL parsing for HTTPS scheme validation in config Agent-Logs-Url: https://github.com/resq-software/crates/sessions/f3527d5b-6f5e-48a4-a3b5-f748eed315bf Co-authored-by: WomB0ComB0 <95197809+WomB0ComB0@users.noreply.github.com> * ci: migrate to reusable rust-ci, remove clippy.yml + deny.yml (#71) * ci: migrate to reusable rust-ci in resq-software/.github Replaces inline test/fmt jobs in ci.yml with a thin caller of the org-wide reusable workflow. Subsumes clippy.yml and deny.yml (the reusable wraps fmt + clippy + test + coverage + cargo-deny into one dispatch). Adds a top-level `required` aggregator job that emits the status-check context consumed by the org ruleset `default-branch-baseline` (id 15191038, currently evaluate mode). Pinned to the PR#12 tip SHA; re-pin after that PR lands. Parity: - fmt: cargo fmt --all --check - clippy: cargo clippy --all-targets --all-features -- -D warnings - test: cargo llvm-cov --workspace --lcov (codecov upload) - deny: cargo-deny check --all-features (via deny.toml) Security scanning remains in security.yml — independent cadence. * ci: re-pin reusable workflow ref to resq-software/.github main Updates @sha from the feat-branch tip to the merge commit of resq-software/.github#12 (f4b51a620aa1bf89c0bce4f434b36f92ff7d517d). Functionally equivalent — same content — but pins to a ref that now exists on main rather than a closed PR branch. * fix(ci): resolve clippy explicit_counter_loop + cargo-deny failures The migration to reusable rust-ci.yml surfaced two pre-existing issues that the old per-workflow CI didn't catch: 1. clippy::explicit_counter_loop in resq-dsa::rabin_karp Replaced `let mut i = 1; for c in chars { …; i += 1; }` with `for (i, c) in (1..).zip(chars) { … }`. Idiomatic, avoids the lint introduced by Rust 1.95, behavior unchanged. 2. cargo-deny failures — three licenses + one advisory: - RUSTSEC-2026-0099 (rustls-webpki name-constraint bug) → `cargo update -p rustls-webpki` bumps 0.103.10 → 0.103.12 (Cargo.lock delta only). - CDDL-1.0 (inferno, via resq-flame profiling stack) added to licenses.allow with rationale. - CDLA-Permissive-2.0 (webpki-root-certs root cert DB) added to licenses.allow. - MPL-2.0 (colored) added as a crate-specific exception rather than widening the org-wide MPL allowance. Verified locally: `cargo deny --all-features check` → all four sections (advisories, bans, licenses, sources) pass. * fix(clippy): address Rust 1.95 warnings surfaced by reusable rust-ci CI on the migrated rust-ci.yml (which now runs clippy across the whole workspace) surfaced four pedantic/default warnings that were elevated to errors by -D warnings: 1. resq-clean src/main.rs:100 map_unwrap_or -> fs::metadata(&path).map_or(0, |m| m.len()) 2. resq-clean src/main.rs:110 unnecessary_sort_by -> entries.sort_by_key(|e| std::cmp::Reverse(e.size)) 3. resq-cli src/commands/pre_commit.rs:1043/1048 collapsible_match -> KeyCode::<X> if <cond> => { <body> } 4. resq-cli tests/format.rs:19 map_unwrap_or -> Command::new("which").arg(cmd).output() .is_ok_and(|o| o.status.success()) All four are semantically-identical refactors; no behavior change. Verified clean locally against rustc 1.95.0: cargo +stable clippy --all-targets --all-features -- -D warnings * ci: re-pin reusable rust-ci to .github main (post-#13 merge) Picks up the taiki-e SHA correction and the Docker-bound cargo-deny replacement (dtolnay/rust-toolchain + taiki-e install cargo-deny + direct cargo deny check) so the run respects this repo's rust-toolchain.toml musl pin. Updates @f4b51a6 → @2e2af3e5e32b646480a1cb1b41875e5073020814. * fix(resq-ai): address review feedback — client reuse, config warnings, gitleaks - Add .gitleaks.toml to allowlist test placeholder strings in resq-ai config tests, fixing CI Secret Scanning failure - Pass --config .gitleaks.toml in codeql.yml gitleaks step - Reuse a single reqwest::Client across provider calls instead of creating one per request (connection pooling) - Fix config cascade docstring to match actual precedence (project > home, not home > project) - Warn on malformed TOML config instead of silently ignoring - Rename OpenAI max_tokens → max_completion_tokens (deprecated field) - Fix Rust 1.95 clippy warnings in resq-clean and pre_commit --------- Signed-off-by: Mike Odnis <mike@mikeodnis.dev> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Summary
Introduce org-wide reusable workflows consumer repos can call via
uses: resq-software/.github/.github/workflows/<name>@<sha>, plus thegovernance API payloads needed to activate org-level rulesets on top.
New workflows
rust-ci.yml— fmt · clippy · test (opt coverage) · cargo-denypython-ci.yml— ruff · mypy · pytest matrix (3.11/3.12/3.13) · uv buildnode-ci.yml— bun/npm/pnpm/yarn with configurable lint/typecheck/test/builddocker-publish.yml— buildx → GHCR with SLSA build-provenance attestation (OIDC + sigstore)required.yml— aggregator; dispatches bylanginput; always callssecurity-scan.yml; emits singlerequiredstatus-check contextAll third-party actions SHA-pinned with trailing
# <tag>for Dependabot.Every job starts with
step-security/harden-runnerinauditmode tomatch the pattern already in
security-scan.yml.Governance payloads (ops/)
ops/governance-payloads.md— custom-property schema, repo assignments for all 14 repos, Ruleset A (default-branch baseline), Ruleset B (critical-tier extras), plus verification + rollback commands. Apply order: properties → values → rulesets.What this does NOT do
gh api --input, gated on review.dotnet-ci.ymlorcpp-ci.ymlyet (1 and 2 consumers respectively; deferred). When they land,required.ymlneeds ~20 lines added to dispatch them.Ruleset-A pre-flight
Ruleset A enforces
require_code_owner_review. Any repo withoutCODEOWNERSwill block every PR until one lands.resq-protocurrently lacksCODEOWNERS— ship one before activating Ruleset A, or excluderesq-protofrom the baseline ruleset'sconditions.repository_nameduring bake.Test plan
actionlintpasses on the five new workflow filesresq-software/<throwaway>/.github/workflows/ci.ymlcallingrequired.ymlwithlang: node; confirm therequiredstatus check appears greenSummary by CodeRabbit
Chores
Documentation