diff --git a/.github-gen/velnor-workflow.toml b/.github-gen/velnor-workflow.toml deleted file mode 100644 index 2be57d0..0000000 --- a/.github-gen/velnor-workflow.toml +++ /dev/null @@ -1,20 +0,0 @@ -schema = 2 -[generator] -repository = "jackin-project/jackin-dev" -revision = "be61acbbbfb71db6b51337e547e39de049d4fc15" -[policy] -ruleset_required_status_checks = ["ci-required", "Policy"] -ruleset_external_status_checks = ["DCO"] -[workflow] -providers = ["github-hosted"] -automatic_providers = ["github-hosted"] -default_branch = "main" -[workflow.selectors.github-hosted] -runs_on = ["ubuntu-24.04"] -[[units]] -id = "reuse" -kind = "docs" -root = "." -watch = ["REUSE.toml", "LICENSES/**", "mise.toml", "mise.lock"] -ci_tasks = ["ci"] -mise_tools = ["python", "pipx:reuse"] diff --git a/.github-gen/visibility.toml b/.github-gen/visibility.toml deleted file mode 100644 index 3b565ff..0000000 --- a/.github-gen/visibility.toml +++ /dev/null @@ -1,2 +0,0 @@ -repository = "jackin-project/jackin-dev" -visibility = "public" diff --git a/.github/AGENTS.md b/.github/AGENTS.md index 729b02f..136957d 100644 --- a/.github/AGENTS.md +++ b/.github/AGENTS.md @@ -1,5 +1,32 @@ -# Generated files +# Generated by Velnor Actions 0.1.0; edit .velnor/config.toml and regenerate. +# Generated GitHub Workflows and Configurations -Everything under `.github` is generated by [velnor-workflow](https://github.com/tailrocks/velnor/tree/main/crates/velnor-workflow). +All files and subdirectories under `.github/` are generated by `velnor-actions` from [tailrocks/velnor-new](https://github.com/tailrocks/velnor-new). -Never hand-edit this directory. Changes to generated behavior require a Velnor PR: first research, analyze, and independently verify a generic solution, never a repository-specific workaround. Keep generation inputs outside `.github`, then regenerate. Root `AGENTS.md` rules still apply. +## Do Not Hand-Edit `.github/` + +- **Never hand-edit any file inside `.github/`**: Every manual change will be overwritten on the next run of `velnor-actions generate`. +- **Keep generation inputs outside `.github/`**: Repository configuration belongs in `.velnor/config.toml`, workspace manifests, and toolchain settings outside `.github/`. +- **Regenerate via CLI**: Apply configuration changes by running `velnor-actions generate`. + +## Workflow Issues and Enhancements + +When issues, bugs, limitations, or enhancements are observed in generated GitHub Actions workflows: + +1. **Do NOT patch `.github/` directly or create repo-specific workarounds**: + - Avoid local ad-hoc patches, overrides, or shims inside `.github/`. +2. **Research generic solutions in `velnor-actions` / `velnor-new`**: + - Investigate solutions in the upstream generator: [tailrocks/velnor-new](https://github.com/tailrocks/velnor-new). + - Verify that potential changes align with the vision and architectural invariants of Velnor Actions (generic workflow generator, strict safety invariants, zero legacy, fast CI). +3. **Analyze and verify with subagents**: + - Compare proposed designs against the current implementation in `velnor-new` using detailed subagent analysis. + - Use multiple subagents to review the concept, implementation details, and edge cases. +4. **Submit PR to upstream `velnor-new`**: + - Only after thorough verification and review, submit a PR to `velnor-new` (https://github.com/tailrocks/velnor-new). +5. **Regenerate workflows**: + - After the fix is merged in `velnor-new`, update/regenerate the workflows using `velnor-actions generate`. + +## Root Repository Rules + +- If a root `AGENTS.md` is present in this repository, all of its rules, invariants, and guidelines apply here as well. +- In case of conflict, stricter safety, correctness, and verification standards take precedence. diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index c2cd198..a511b22 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -1,7 +1,7 @@ -# Generated by velnor-workflow. Regenerate; do not hand-edit. -# Runner labels are derived from the generated workflow contract. -# The actionlint catalog can lag GitHub-hosted labels; keep this allowlist generated. +# Generated by Velnor Actions 0.1.0; edit .velnor/config.toml and regenerate. + +config-variables: [] self-hosted-runner: labels: - - ubuntu-24.04 + - ubuntu-26.04 diff --git a/.github/ci/.github-actions-generator-state b/.github/ci/.github-actions-generator-state deleted file mode 100644 index b502622..0000000 --- a/.github/ci/.github-actions-generator-state +++ /dev/null @@ -1,18 +0,0 @@ -# Generated ownership state; do not edit. -schema = 2 -[inputs] -config e0b05e7602fb7a1c -scan 86b0642f49c862b8 -generator 69 -[outputs] -.github/AGENTS.md 02ebb7198c435e9b -.github/actionlint.yaml c642e98fce5b1906 -.github/ci/project.toml f2239a3864054197 -.github/workflows/ci-main.yml 8b2cf2bc453561af -.github/workflows/ci-policy.yml f58d3e5e1827497e -.github/workflows/ci-pr.yml f8403d227851a4f2 -.github/workflows/ci-unit-docs.yml 69090c4b67ece6b5 -.github/workflows/maintenance.yml ff88945f34f71efd -.github/workflows/nightly.yml 0659fbb3d6af2359 -config/fleet/velnor-host.env d14b419216449423 -.github/CLAUDE.md 78a740c6ccf6937a diff --git a/.github/ci/project.toml b/.github/ci/project.toml deleted file mode 100644 index a1e7996..0000000 --- a/.github/ci/project.toml +++ /dev/null @@ -1,40 +0,0 @@ -# Generated by velnor-workflow. Regenerate; do not hand-edit. -schema = 3 -repository = "jackin-project/jackin-dev" -profile = "generic" -verified = true -default_branch = "main" -providers = ["github-hosted"] -automatic_providers = ["github-hosted"] -default_dispatch_providers = ["github-hosted"] - -[analysis] -method = "static-filesystem-and-manifest-inspection" -detected = [] -limitations = ["Project code, build scripts, task runners, and commands are never executed during analysis.", "Release, signing, registry, deployment, branch-protection, and runner-capability contracts remain explicit manual inputs."] - -[workflow] -files = ["ci-main.yml", "ci-policy.yml", "ci-pr.yml", "ci-unit-docs.yml", "maintenance.yml", "nightly.yml"] - -[release] -enabled = false -reason = "Release is fail-closed. Enable only after declaring immutable artifact, registry, provenance, and tag-protection policy." - -[[unit]] -id = "reuse" -label = "" -kind = "docs" -root = "." -watch = ["LICENSES/**", "REUSE.toml", "mise.lock", "mise.toml"] -pr_commands = ["mise run ci"] -full_commands = ["mise run ci"] -platform = "linux-x64" -trust = "untrusted-ok" -[unit.capabilities] -docker = false -nested_privileged_docker = false -buildx_compose = false -testcontainers = false -services_with_readiness = false -browser_binaries = false -native_macos_arm64 = false diff --git a/.github/workflows/ci-main.yml b/.github/workflows/ci-main.yml deleted file mode 100644 index facea06..0000000 --- a/.github/workflows/ci-main.yml +++ /dev/null @@ -1,402 +0,0 @@ -# Generated by velnor-workflow. Regenerate; do not hand-edit. -name: CI / Main -run-name: CI / main · ${{ github.event_name }} · ${{ github.ref_name }} - -on: - push: - branches: [main] - workflow_dispatch: - inputs: - scope: - description: Verification scope - required: true - default: full - type: choice - options: - - affected - - full - base_sha: - description: Git ref or SHA used as the affected-selection base - required: false - default: refs/heads/main - type: string - - -concurrency: - group: ci-${{ github.workflow }}-${{ github.run_id }} - cancel-in-progress: false - -permissions: - actions: read - contents: read - -jobs: - plan: - name: "Control / Planning" - runs-on: ubuntu-24.04 - outputs: - scope: ${{ steps.plan.outputs.scope }} - base_sha: ${{ steps.plan.outputs.base_sha }} - head_sha: ${{ steps.plan.outputs.head_sha }} - units: ${{ steps.plan.outputs.units }} - unit_ids: ${{ steps.plan.outputs.unit_ids }} - full_units: ${{ steps.plan.outputs.full_units }} - plan_digest: ${{ steps.plan.outputs.plan_digest }} - excluded: ${{ steps.plan.outputs.excluded }} - docs_matrix: ${{ steps.plan.outputs.docs_matrix }} - # Presence-only no-work marker: `planned_no_work` is `true` or absent, - # never `false`. Consumers MUST branch - # `needs.plan.outputs.planned_no_work == 'true'` for the no-work path; - # any other value (including absent) means the plan selected work. - planned_no_work: ${{ steps.plan.outputs.planned_no_work }} - no_work_reason: ${{ steps.plan.outputs.no_work_reason }} - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 - persist-credentials: false - - name: Set up Velnor workflow runtime - id: runtime - uses: tailrocks/velnor/.github/actions/setup-velnor-workflow@be61acbbbfb71db6b51337e547e39de049d4fc15 - with: - rev: be61acbbbfb71db6b51337e547e39de049d4fc15 - - name: Set trusted workflow policy revision - run: echo "VELNOR_WORKFLOW_POLICY_REVISION=be61acbbbfb71db6b51337e547e39de049d4fc15" >> "$GITHUB_ENV" - - name: Select affected units - id: plan - env: - EVENT_NAME: ${{ github.event_name }} - CI_SCOPE_OVERRIDE: ${{ github.event.inputs.scope || '' }} - BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.inputs.base_sha || github.event.before || 'refs/heads/main' }} - HEAD_SHA: ${{ github.sha }} - VELNOR_PROVIDERS: github-hosted - VELNOR_EVENT_TRUSTED: ${{ (!(github.event_name == 'pull_request' && (github.event.pull_request.head.repo.fork || github.event.pull_request.user.type == 'Bot'))) && 'true' || 'false' }} - VELNOR_EXPECTED_WORK_FILE: .velnor-ci-expected-work/expected-work.json - run: | - set -euo pipefail - if [[ -z "${CI_SCOPE_OVERRIDE:-}" ]]; then unset CI_SCOPE_OVERRIDE; fi - mkdir -p .velnor-ci-expected-work - velnor-workflow plan --config .github/ci/project.toml - - - name: Publish expected work - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: velnor-expected-work - path: .velnor-ci-expected-work/expected-work.json - if-no-files-found: error - retention-days: 7 - - name: Prepare Velnor workflow runtime - shell: bash - env: - EXPECTED_REVISION: be61acbbbfb71db6b51337e547e39de049d4fc15 - run: | - set -euo pipefail - stage="$RUNNER_TEMP/velnor-workflow-runtime" - rm -rf "$stage" - mkdir -p "$stage" - src="$(command -v velnor-workflow)" - install -m 0755 "$src" "$stage/velnor-workflow" - digest="$(sha256sum "$stage/velnor-workflow" | awk '{print $1}')" - policy_src="${VELNOR_WORKFLOW_PINNED_BINARY:-$src}" - install -m 0755 "$policy_src" "$stage/velnor-workflow-policy" - policy_revision="$("$stage/velnor-workflow-policy" --revision)" - policy_closure="$("$stage/velnor-workflow-policy" --closure)" - [[ "$policy_closure" == "${{ steps.runtime.outputs.closure }}" ]] || { echo "::error::policy runtime reports closure $policy_closure, expected ${{ steps.runtime.outputs.closure }}" >&2; exit 1; } - policy_digest="$(sha256sum "$stage/velnor-workflow-policy" | awk '{print $1}')" - jq -n --arg repository "$GITHUB_REPOSITORY" --arg revision "$EXPECTED_REVISION" --arg closure "${{ steps.runtime.outputs.closure }}" --arg head_branch "${{ github.ref_name }}" --arg platform "${{ runner.os }}-${{ runner.arch }}" --arg run_id "$GITHUB_RUN_ID" --arg job_id "${{ github.job }}" --arg binary_sha256 "$digest" --arg policy_revision "$policy_revision" --arg policy_closure "$policy_closure" --arg policy_binary_sha256 "$policy_digest" '{repository: $repository, revision: $revision, closure: $closure, head_branch: $head_branch, platform: $platform, run_id: $run_id, job_id: $job_id, binary_sha256: $binary_sha256, policy_revision: $policy_revision, policy_closure: $policy_closure, policy_binary_sha256: $policy_binary_sha256}' > "$stage/manifest.json" - - name: Publish Velnor workflow runtime - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: velnor-workflow-runtime-be61acbbbfb71db6b51337e547e39de049d4fc15-${{ runner.os }}-${{ runner.arch }} - path: ${{ runner.temp }}/velnor-workflow-runtime - if-no-files-found: error - retention-days: 7 - policy: - name: Policy - runs-on: ubuntu-24.04 - timeout-minutes: 20 - # Trust invariant: this job runs the base branch's Stage-0 validator - # product against the audited tree under pull_request_target. It holds - # `contents: read` only, references no secrets, persists no credentials, - # and never compiles. When the audited tree differs from the declared - # pin's render, it additionally EXECUTES the PR run's prebuilt - # candidate generator — PR-built code, same-repository runs only, bound - # to the audited tree by manifest closure plus binary digest before - # execution — with no secret references, no persisted credentials, the - # read-only github.token confined to the Acquire/Ruleset API steps, - # and both candidate exec points tokenless. - permissions: - contents: read - steps: - - name: Checkout repository history - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - path: policy-checkout - fetch-depth: 0 - persist-credentials: false - - name: Check out audited head - working-directory: policy-checkout - env: - HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - run: | - set -euo pipefail - if ! git cat-file -e "$HEAD_SHA^{commit}" 2>/dev/null; then - git fetch --no-tags "$GITHUB_SERVER_URL/$HEAD_REPOSITORY" "$HEAD_SHA" - fi - git checkout --quiet --detach "$HEAD_SHA" - - name: Set up Velnor workflow runtime - uses: tailrocks/velnor/.github/actions/setup-velnor-workflow@be61acbbbfb71db6b51337e547e39de049d4fc15 - with: - rev: be61acbbbfb71db6b51337e547e39de049d4fc15 - checkout-path: ${{ github.workspace }}/policy-checkout - - name: Read declared generator pin - id: pin - working-directory: policy-checkout - run: | - set -euo pipefail - pin="$(sed -n -E 's/^[[:space:]]*revision[[:space:]]*=[[:space:]]*"([0-9a-f]{40})".*/\1/p' .github-gen/velnor-workflow.toml | head -n 1)" - test "$pin" != '' || pin="$(sed -n -E 's/^.*VELNOR_WORKFLOW_POLICY_REVISION:[[:space:]]*([0-9a-f]{40}).*/\1/p' .github/workflows/ci-policy.yml | head -n 1)" - test "$pin" != '' || { echo "::error::audited tree declares no generator pin" >&2; exit 1; } - echo "value=$pin" >> "$GITHUB_OUTPUT" - - name: Set up declared generator product - id: renderer - if: steps.pin.outputs.value != 'be61acbbbfb71db6b51337e547e39de049d4fc15' - uses: tailrocks/velnor/.github/actions/setup-velnor-workflow@be61acbbbfb71db6b51337e547e39de049d4fc15 - with: - rev: ${{ steps.pin.outputs.value }} - checkout-path: ${{ github.workspace }}/policy-checkout - - name: Resolve declared generator product - if: steps.pin.outputs.value != 'be61acbbbfb71db6b51337e547e39de049d4fc15' - run: | - set -euo pipefail - binary="$HOME/.cache/velnor/workflow-runtime/${{ steps.renderer.outputs.closure }}/bin/velnor-workflow" - test -x "$binary" - echo "VELNOR_WORKFLOW_PINNED_BINARY=$binary" >> "$GITHUB_ENV" - - name: Resolve required status checks - env: - GH_TOKEN: ${{ github.token }} - DEFAULT_BRANCH: main - DECLARED_RULESET_CONTEXTS: DCO,Policy,ci-required - run: | - set -euo pipefail - stderr="$(mktemp)" - trap 'rm -f "$stderr"' EXIT - if contexts="$(gh api "repos/$GITHUB_REPOSITORY/rulesets?includes_parents=true" 2>"$stderr" \ - | jq -r '.[] | select(.target == "branch" and .enforcement == "active") | .id' \ - | while read -r id; do gh api "repos/$GITHUB_REPOSITORY/rulesets/$id"; done \ - | jq -r --arg branch "refs/heads/$DEFAULT_BRANCH" 'select(.conditions.ref_name.include | any(. == "~DEFAULT_BRANCH" or . == "~ALL" or . == $branch)) | .rules[] | select(.type == "required_status_checks") | .parameters.required_status_checks[].context' \ - | sort -u | paste -sd, -)"; then - : - elif grep -qE '(HTTP 403|Upgrade to GitHub Team)' "$stderr"; then - echo "::warning::rulesets API returned 403; falling back to declared contexts [$DECLARED_RULESET_CONTEXTS]" - contexts="$DECLARED_RULESET_CONTEXTS" - else - cat "$stderr" >&2 - exit 1 - fi - echo "RULESET_CONTEXTS=$contexts" >> "$GITHUB_ENV" - - name: Enforce workflow policy - env: - WORKFLOW_ROOT: ${{ github.workspace }}/policy-checkout - HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - BASE_SHA: ${{ github.event.pull_request.base.sha || github.sha }} - VELNOR_WORKFLOW_POLICY_REVISION: be61acbbbfb71db6b51337e547e39de049d4fc15 - run: | - set -euo pipefail - velnor-workflow policy \ - --workflow-root "$WORKFLOW_ROOT" \ - --head-sha "$HEAD_SHA" \ - --base-sha "$BASE_SHA" \ - --candidate-manifest "${VELNOR_WORKFLOW_CANDIDATE_MANIFEST:-}" \ - --ruleset-contexts "$RULESET_CONTEXTS" - - - name: Set up actionlint - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 - with: - install_args: actionlint@1.7.12 - cache: false - - name: Lint caller workflows - working-directory: policy-checkout - env: - MISE_NO_CONFIG: "1" - run: mise exec actionlint@1.7.12 -- actionlint - github-hosted-reuse: - name: "Documentation · · github-hosted — reuse" - if: ${{ always() && needs.plan.result == 'success' && needs.policy.result == 'success' && contains(needs.plan.outputs.units, '"unit_id":"reuse"') && (true) }} - needs: [plan, policy] - uses: ./.github/workflows/ci-unit-docs.yml - with: - unit: reuse - provider: github-hosted - selected_units: ${{ needs.plan.outputs.units }} - selected_unit_ids: ${{ needs.plan.outputs.unit_ids }} - scope: ${{ needs.plan.outputs.scope }} - full_units: ${{ needs.plan.outputs.full_units }} - plan_digest: ${{ needs.plan.outputs.plan_digest }} - base_sha: ${{ needs.plan.outputs.base_sha }} - head_sha: ${{ needs.plan.outputs.head_sha }} - mise_tools: "python pipx:reuse" - unit_platform: linux-x64 - unit_trust: untrusted-ok - unit_admission: github-hosted - ci-required: - name: ci-required - if: ${{ always() }} - needs: [plan, policy, github-hosted-reuse] - runs-on: ubuntu-24.04 - timeout-minutes: 5 - steps: - - name: Download Velnor workflow runtime - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: velnor-workflow-runtime-be61acbbbfb71db6b51337e547e39de049d4fc15-${{ runner.os }}-${{ runner.arch }} - path: .velnor-workflow-runtime - - name: Verify Velnor workflow runtime - shell: bash - env: - EXPECTED_REVISION: be61acbbbfb71db6b51337e547e39de049d4fc15 - run: | - set -euo pipefail - manifest=.velnor-workflow-runtime/manifest.json - jq -e --arg revision "$EXPECTED_REVISION" --arg repository "$GITHUB_REPOSITORY" --arg platform "${RUNNER_OS}-${RUNNER_ARCH}" --arg run_id "$GITHUB_RUN_ID" '.revision == $revision and .repository == $repository and .platform == $platform and .run_id == $run_id and (.run_id | test("^[0-9]+$")) and .job_id != "" and (.binary_sha256 | test("^[0-9a-f]{64}$")) and (.policy_binary_sha256 | test("^[0-9a-f]{64}$")) and (.closure | test("^[0-9a-f]{64}$")) and (.policy_closure | test("^[0-9a-f]{64}$"))' "$manifest" >/dev/null - expected="$(jq -er '.binary_sha256' "$manifest")" - actual="$(sha256sum .velnor-workflow-runtime/velnor-workflow | awk '{print $1}')" - [[ "$actual" == "$expected" ]] || { echo "::error::runtime digest mismatch" >&2; exit 1; } - expected="$(jq -er '.policy_binary_sha256' "$manifest")" - actual="$(sha256sum .velnor-workflow-runtime/velnor-workflow-policy | awk '{print $1}')" - [[ "$actual" == "$expected" ]] || { echo "::error::policy runtime digest mismatch" >&2; exit 1; } - - name: Add Velnor workflow runtime to PATH - shell: bash - env: - EXPECTED_REVISION: be61acbbbfb71db6b51337e547e39de049d4fc15 - run: | - set -euo pipefail - home="$RUNNER_TEMP/velnor-workflow-runtime-artifact" - install -Dm0755 .velnor-workflow-runtime/velnor-workflow "$home/bin/velnor-workflow" - install -Dm0755 .velnor-workflow-runtime/velnor-workflow-policy "$home/bin/velnor-workflow-policy" - expected_closure="$(jq -er '.policy_closure' .velnor-workflow-runtime/manifest.json)" - reported="$("$home/bin/velnor-workflow-policy" --closure)" - [[ "$reported" == "$expected_closure" ]] || { echo "::error::policy runtime reports closure $reported, expected $expected_closure" >&2; exit 1; } - echo "$home/bin" >> "$GITHUB_PATH" - echo "VELNOR_WORKFLOW_PINNED_BINARY=$home/bin/velnor-workflow-policy" >> "$GITHUB_ENV" - - name: Download expected work - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: velnor-expected-work - path: .velnor-ci-expected-work - - name: Download reported unit results - # A no-work plan runs no unit jobs, so zero result artifacts is the - # expected case there — and the aggregate fails a real-work plan with - # zero records anyway. Tolerate the empty download; never the verdict. - continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - pattern: velnor-result-* - merge-multiple: true - path: .velnor-ci-results - - name: Collect reported unit results - shell: bash - run: | - set -euo pipefail - shopt -s nullglob - mkdir -p .velnor-ci-results - files=(.velnor-ci-results/result-*.json) - for file in "${files[@]}"; do - if jq -e 'any(.results[]?; has("reused_from"))' "$file" >/dev/null; then - echo "::error::$file carries reused_from without a validate_reuse decision; render emits no reused results" >&2 - exit 1 - fi - done - if (( ${#files[@]} == 0 )); then - printf '{"results":[]}\n' > .velnor-ci-results.json - else - jq -s '{results: ([.[].results // empty] | add // [])}' "${files[@]}" > .velnor-ci-results.json - fi - echo "collected $(jq '.results | length' .velnor-ci-results.json) reported result(s) from ${#files[@]} record file(s)" - - name: Score expected work against reported results - env: - BASE_SHA: ${{ needs.plan.outputs.base_sha }} - HEAD_SHA: ${{ needs.plan.outputs.head_sha }} - shell: bash - run: | - set -euo pipefail - velnor-workflow aggregate --expected .velnor-ci-expected-work/expected-work.json --results .velnor-ci-results.json - - name: Validate generated stack results - env: - NEEDS_JSON: ${{ toJSON(needs) }} - SELECTED_UNITS: ${{ needs.plan.outputs.units }} - PLAN_DIGEST: ${{ needs.plan.outputs.plan_digest }} - EXCLUDED: ${{ needs.plan.outputs.excluded }} - EXPECTED_CALLERS: "[{\"job_id\":\"github-hosted-reuse\",\"provider\":\"github-hosted\",\"unit_id\":\"reuse\"}]" - PROVIDER_ADMITTED_GITHUB_HOSTED: ${{ true }} - shell: bash - run: | - set -euo pipefail - if [[ -z "$PLAN_DIGEST" ]]; then - echo "plan did not freeze a plan digest: the expected set has no identity" >&2 - exit 1 - fi - echo "verdict binds plan digest $PLAN_DIGEST" - result_for_job() { - jq -r --arg job "$1" '.[$job].result // empty' <<<"$NEEDS_JSON" - } - plan_expects() { - [[ "$(jq -r --arg unit "$1" --arg provider "$2" '[.[] | select(.unit_id == $unit) | .providers[] | select(. == $provider)] | length' <<<"$SELECTED_UNITS")" -gt 0 ]] - } - if ! jq -e --argjson expected "$EXPECTED_CALLERS" '($expected | type == "array") and all($expected[]; type == "object" and (.unit_id | type) == "string" and (.provider | type) == "string" and (.job_id | type) == "string")' -n; then - echo "generated required-caller contract is malformed" >&2 - exit 1 - fi - if ! jq -e --argjson expected "$EXPECTED_CALLERS" 'type == "array" and (map(.unit_id) | unique | length) == length and all(.[]; . as $entry | ($entry | type) == "object" and ($entry.unit_id | type) == "string" and ($entry.unit_id | length) > 0 and ($entry.providers | type) == "array" and ($entry.providers | length) > 0 and (($entry.providers | map(type == "string" and length > 0) | all)) and (($entry.providers | unique | length) == ($entry.providers | length)) and all($entry.providers[]; . as $provider | any($expected[]; .unit_id == $entry.unit_id and .provider == $provider)))' <<<"$SELECTED_UNITS" >/dev/null; then - echo "plan selected-unit output contains an unknown, duplicate, empty, or unmapped obligation" >&2 - exit 1 - fi - if ! jq -e 'type == "object"' <<<"$NEEDS_JSON" >/dev/null; then - echo "workflow needs output is malformed" >&2 - exit 1 - fi - result="$(result_for_job plan)" - if [[ "$result" != success ]]; then - echo "required CI prerequisite plan did not pass: $result" >&2 - exit 1 - fi - result="$(result_for_job policy)" - if [[ "$result" != success ]]; then - echo "required CI prerequisite policy did not pass: $result" >&2 - exit 1 - fi - if plan_expects "reuse" "github-hosted"; then - result="$(result_for_job github-hosted-reuse)" - if [[ "$PROVIDER_ADMITTED_GITHUB_HOSTED" == true ]]; then - case "$result" in - success) ;; - skipped) echo "expected CI job github-hosted-reuse was skipped: a skipped expected result cannot pass" >&2; exit 1 ;; - cancelled) echo "expected CI job github-hosted-reuse was cancelled: a cancelled expected result cannot pass" >&2; exit 1 ;; - *) echo "expected CI job github-hosted-reuse did not pass: $result" >&2; exit 1 ;; - esac - else - case "$result" in - skipped) ;; - *) echo "selected CI job github-hosted-reuse ran outside its provider admission (PROVIDER_ADMITTED_GITHUB_HOSTED=$PROVIDER_ADMITTED_GITHUB_HOSTED): $result" >&2; exit 1 ;; - esac - fi - else - result="$(result_for_job github-hosted-reuse)" - case "$result" in - skipped) ;; - success) echo "unexpected CI job github-hosted-reuse succeeded outside the expected set: the plan did not declare it" >&2; exit 1 ;; - *) echo "unexpected CI job github-hosted-reuse ran outside the expected set: $result" >&2; exit 1 ;; - esac - fi - required: - name: "Control / Required" - if: ${{ always() }} - needs: [ci-required] - runs-on: ubuntu-24.04 - timeout-minutes: 5 - steps: - - name: Mirror CI / Required - if: ${{ needs.ci-required.result != 'success' }} - run: exit 1 diff --git a/.github/workflows/ci-policy.yml b/.github/workflows/ci-policy.yml deleted file mode 100644 index 684823f..0000000 --- a/.github/workflows/ci-policy.yml +++ /dev/null @@ -1,126 +0,0 @@ -# Generated by velnor-workflow. Regenerate; do not hand-edit. -name: Velnor workflow policy - -on: - pull_request_target: - types: [opened, synchronize, reopened] - workflow_dispatch: - -concurrency: - group: policy-${{ github.repository }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - policy: - name: Policy - runs-on: ubuntu-24.04 - timeout-minutes: 20 - # Trust invariant: this job runs the base branch's Stage-0 validator - # product against the audited tree under pull_request_target. It holds - # `contents: read` only, references no secrets, persists no credentials, - # and never compiles. When the audited tree differs from the declared - # pin's render, it additionally EXECUTES the PR run's prebuilt - # candidate generator — PR-built code, same-repository runs only, bound - # to the audited tree by manifest closure plus binary digest before - # execution — with no secret references, no persisted credentials, the - # read-only github.token confined to the Acquire/Ruleset API steps, - # and both candidate exec points tokenless. - permissions: - contents: read - steps: - - name: Checkout repository history - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - path: policy-checkout - fetch-depth: 0 - persist-credentials: false - - name: Check out audited head - working-directory: policy-checkout - env: - HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - run: | - set -euo pipefail - if ! git cat-file -e "$HEAD_SHA^{commit}" 2>/dev/null; then - git fetch --no-tags "$GITHUB_SERVER_URL/$HEAD_REPOSITORY" "$HEAD_SHA" - fi - git checkout --quiet --detach "$HEAD_SHA" - - name: Set up Velnor workflow runtime - uses: tailrocks/velnor/.github/actions/setup-velnor-workflow@be61acbbbfb71db6b51337e547e39de049d4fc15 - with: - rev: be61acbbbfb71db6b51337e547e39de049d4fc15 - checkout-path: ${{ github.workspace }}/policy-checkout - - name: Read declared generator pin - id: pin - working-directory: policy-checkout - run: | - set -euo pipefail - pin="$(sed -n -E 's/^[[:space:]]*revision[[:space:]]*=[[:space:]]*"([0-9a-f]{40})".*/\1/p' .github-gen/velnor-workflow.toml | head -n 1)" - test "$pin" != '' || pin="$(sed -n -E 's/^.*VELNOR_WORKFLOW_POLICY_REVISION:[[:space:]]*([0-9a-f]{40}).*/\1/p' .github/workflows/ci-policy.yml | head -n 1)" - test "$pin" != '' || { echo "::error::audited tree declares no generator pin" >&2; exit 1; } - echo "value=$pin" >> "$GITHUB_OUTPUT" - - name: Set up declared generator product - id: renderer - if: steps.pin.outputs.value != 'be61acbbbfb71db6b51337e547e39de049d4fc15' - uses: tailrocks/velnor/.github/actions/setup-velnor-workflow@be61acbbbfb71db6b51337e547e39de049d4fc15 - with: - rev: ${{ steps.pin.outputs.value }} - checkout-path: ${{ github.workspace }}/policy-checkout - - name: Resolve declared generator product - if: steps.pin.outputs.value != 'be61acbbbfb71db6b51337e547e39de049d4fc15' - run: | - set -euo pipefail - binary="$HOME/.cache/velnor/workflow-runtime/${{ steps.renderer.outputs.closure }}/bin/velnor-workflow" - test -x "$binary" - echo "VELNOR_WORKFLOW_PINNED_BINARY=$binary" >> "$GITHUB_ENV" - - name: Resolve required status checks - env: - GH_TOKEN: ${{ github.token }} - DEFAULT_BRANCH: main - DECLARED_RULESET_CONTEXTS: DCO,Policy,ci-required - run: | - set -euo pipefail - stderr="$(mktemp)" - trap 'rm -f "$stderr"' EXIT - if contexts="$(gh api "repos/$GITHUB_REPOSITORY/rulesets?includes_parents=true" 2>"$stderr" \ - | jq -r '.[] | select(.target == "branch" and .enforcement == "active") | .id' \ - | while read -r id; do gh api "repos/$GITHUB_REPOSITORY/rulesets/$id"; done \ - | jq -r --arg branch "refs/heads/$DEFAULT_BRANCH" 'select(.conditions.ref_name.include | any(. == "~DEFAULT_BRANCH" or . == "~ALL" or . == $branch)) | .rules[] | select(.type == "required_status_checks") | .parameters.required_status_checks[].context' \ - | sort -u | paste -sd, -)"; then - : - elif grep -qE '(HTTP 403|Upgrade to GitHub Team)' "$stderr"; then - echo "::warning::rulesets API returned 403; falling back to declared contexts [$DECLARED_RULESET_CONTEXTS]" - contexts="$DECLARED_RULESET_CONTEXTS" - else - cat "$stderr" >&2 - exit 1 - fi - echo "RULESET_CONTEXTS=$contexts" >> "$GITHUB_ENV" - - name: Enforce workflow policy - env: - WORKFLOW_ROOT: ${{ github.workspace }}/policy-checkout - HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - BASE_SHA: ${{ github.event.pull_request.base.sha || github.sha }} - VELNOR_WORKFLOW_POLICY_REVISION: be61acbbbfb71db6b51337e547e39de049d4fc15 - run: | - set -euo pipefail - velnor-workflow policy \ - --workflow-root "$WORKFLOW_ROOT" \ - --head-sha "$HEAD_SHA" \ - --base-sha "$BASE_SHA" \ - --candidate-manifest "${VELNOR_WORKFLOW_CANDIDATE_MANIFEST:-}" \ - --ruleset-contexts "$RULESET_CONTEXTS" - - - name: Set up actionlint - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 - with: - install_args: actionlint@1.7.12 - cache: false - - name: Lint caller workflows - working-directory: policy-checkout - env: - MISE_NO_CONFIG: "1" - run: mise exec actionlint@1.7.12 -- actionlint diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml deleted file mode 100644 index 1008fc3..0000000 --- a/.github/workflows/ci-pr.yml +++ /dev/null @@ -1,286 +0,0 @@ -# Generated by velnor-workflow. Regenerate; do not hand-edit. -name: CI / PR -run-name: CI / PR · ${{ github.event_name }} · ${{ github.ref_name }} - -on: - pull_request: - workflow_dispatch: - inputs: - scope: - description: Verification scope - required: true - default: affected - type: choice - options: - - affected - - full - base_sha: - description: Git ref or SHA used as the affected-selection base - required: false - default: refs/heads/main - type: string - - -concurrency: - group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -permissions: - actions: read - contents: read - -jobs: - plan: - name: "Control / Planning" - runs-on: ubuntu-24.04 - outputs: - scope: ${{ steps.plan.outputs.scope }} - base_sha: ${{ steps.plan.outputs.base_sha }} - head_sha: ${{ steps.plan.outputs.head_sha }} - units: ${{ steps.plan.outputs.units }} - unit_ids: ${{ steps.plan.outputs.unit_ids }} - full_units: ${{ steps.plan.outputs.full_units }} - plan_digest: ${{ steps.plan.outputs.plan_digest }} - excluded: ${{ steps.plan.outputs.excluded }} - docs_matrix: ${{ steps.plan.outputs.docs_matrix }} - # Presence-only no-work marker: `planned_no_work` is `true` or absent, - # never `false`. Consumers MUST branch - # `needs.plan.outputs.planned_no_work == 'true'` for the no-work path; - # any other value (including absent) means the plan selected work. - planned_no_work: ${{ steps.plan.outputs.planned_no_work }} - no_work_reason: ${{ steps.plan.outputs.no_work_reason }} - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 - persist-credentials: false - - name: Set up Velnor workflow runtime - id: runtime - uses: tailrocks/velnor/.github/actions/setup-velnor-workflow@be61acbbbfb71db6b51337e547e39de049d4fc15 - with: - rev: be61acbbbfb71db6b51337e547e39de049d4fc15 - - name: Set trusted workflow policy revision - run: echo "VELNOR_WORKFLOW_POLICY_REVISION=be61acbbbfb71db6b51337e547e39de049d4fc15" >> "$GITHUB_ENV" - - name: Select affected units - id: plan - env: - EVENT_NAME: ${{ github.event_name }} - CI_SCOPE_OVERRIDE: ${{ github.event.inputs.scope || '' }} - BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.inputs.base_sha || github.event.before || 'refs/heads/main' }} - HEAD_SHA: ${{ github.sha }} - VELNOR_PROVIDERS: github-hosted - VELNOR_EVENT_TRUSTED: ${{ (!(github.event_name == 'pull_request' && (github.event.pull_request.head.repo.fork || github.event.pull_request.user.type == 'Bot'))) && 'true' || 'false' }} - VELNOR_EXPECTED_WORK_FILE: .velnor-ci-expected-work/expected-work.json - run: | - set -euo pipefail - if [[ -z "${CI_SCOPE_OVERRIDE:-}" ]]; then unset CI_SCOPE_OVERRIDE; fi - mkdir -p .velnor-ci-expected-work - velnor-workflow plan --config .github/ci/project.toml - - - name: Publish expected work - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: velnor-expected-work - path: .velnor-ci-expected-work/expected-work.json - if-no-files-found: error - retention-days: 7 - - name: Prepare Velnor workflow runtime - shell: bash - env: - EXPECTED_REVISION: be61acbbbfb71db6b51337e547e39de049d4fc15 - run: | - set -euo pipefail - stage="$RUNNER_TEMP/velnor-workflow-runtime" - rm -rf "$stage" - mkdir -p "$stage" - src="$(command -v velnor-workflow)" - install -m 0755 "$src" "$stage/velnor-workflow" - digest="$(sha256sum "$stage/velnor-workflow" | awk '{print $1}')" - policy_src="${VELNOR_WORKFLOW_PINNED_BINARY:-$src}" - install -m 0755 "$policy_src" "$stage/velnor-workflow-policy" - policy_revision="$("$stage/velnor-workflow-policy" --revision)" - policy_closure="$("$stage/velnor-workflow-policy" --closure)" - [[ "$policy_closure" == "${{ steps.runtime.outputs.closure }}" ]] || { echo "::error::policy runtime reports closure $policy_closure, expected ${{ steps.runtime.outputs.closure }}" >&2; exit 1; } - policy_digest="$(sha256sum "$stage/velnor-workflow-policy" | awk '{print $1}')" - jq -n --arg repository "$GITHUB_REPOSITORY" --arg revision "$EXPECTED_REVISION" --arg closure "${{ steps.runtime.outputs.closure }}" --arg head_branch "${{ github.ref_name }}" --arg platform "${{ runner.os }}-${{ runner.arch }}" --arg run_id "$GITHUB_RUN_ID" --arg job_id "${{ github.job }}" --arg binary_sha256 "$digest" --arg policy_revision "$policy_revision" --arg policy_closure "$policy_closure" --arg policy_binary_sha256 "$policy_digest" '{repository: $repository, revision: $revision, closure: $closure, head_branch: $head_branch, platform: $platform, run_id: $run_id, job_id: $job_id, binary_sha256: $binary_sha256, policy_revision: $policy_revision, policy_closure: $policy_closure, policy_binary_sha256: $policy_binary_sha256}' > "$stage/manifest.json" - - name: Publish Velnor workflow runtime - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: velnor-workflow-runtime-be61acbbbfb71db6b51337e547e39de049d4fc15-${{ runner.os }}-${{ runner.arch }} - path: ${{ runner.temp }}/velnor-workflow-runtime - if-no-files-found: error - retention-days: 7 - github-hosted-reuse: - name: "Documentation · · github-hosted — reuse" - if: ${{ !cancelled() && needs.plan.result == 'success' && contains(needs.plan.outputs.units, '"unit_id":"reuse"') && (true) }} - needs: [plan] - uses: ./.github/workflows/ci-unit-docs.yml - with: - unit: reuse - provider: github-hosted - selected_units: ${{ needs.plan.outputs.units }} - selected_unit_ids: ${{ needs.plan.outputs.unit_ids }} - scope: ${{ needs.plan.outputs.scope }} - full_units: ${{ needs.plan.outputs.full_units }} - plan_digest: ${{ needs.plan.outputs.plan_digest }} - base_sha: ${{ needs.plan.outputs.base_sha }} - head_sha: ${{ needs.plan.outputs.head_sha }} - mise_tools: "python pipx:reuse" - unit_platform: linux-x64 - unit_trust: untrusted-ok - unit_admission: github-hosted - ci-required: - name: ci-required - if: ${{ !cancelled() }} - needs: [plan, github-hosted-reuse] - runs-on: ubuntu-24.04 - timeout-minutes: 5 - steps: - - name: Download Velnor workflow runtime - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: velnor-workflow-runtime-be61acbbbfb71db6b51337e547e39de049d4fc15-${{ runner.os }}-${{ runner.arch }} - path: .velnor-workflow-runtime - - name: Verify Velnor workflow runtime - shell: bash - env: - EXPECTED_REVISION: be61acbbbfb71db6b51337e547e39de049d4fc15 - run: | - set -euo pipefail - manifest=.velnor-workflow-runtime/manifest.json - jq -e --arg revision "$EXPECTED_REVISION" --arg repository "$GITHUB_REPOSITORY" --arg platform "${RUNNER_OS}-${RUNNER_ARCH}" --arg run_id "$GITHUB_RUN_ID" '.revision == $revision and .repository == $repository and .platform == $platform and .run_id == $run_id and (.run_id | test("^[0-9]+$")) and .job_id != "" and (.binary_sha256 | test("^[0-9a-f]{64}$")) and (.policy_binary_sha256 | test("^[0-9a-f]{64}$")) and (.closure | test("^[0-9a-f]{64}$")) and (.policy_closure | test("^[0-9a-f]{64}$"))' "$manifest" >/dev/null - expected="$(jq -er '.binary_sha256' "$manifest")" - actual="$(sha256sum .velnor-workflow-runtime/velnor-workflow | awk '{print $1}')" - [[ "$actual" == "$expected" ]] || { echo "::error::runtime digest mismatch" >&2; exit 1; } - expected="$(jq -er '.policy_binary_sha256' "$manifest")" - actual="$(sha256sum .velnor-workflow-runtime/velnor-workflow-policy | awk '{print $1}')" - [[ "$actual" == "$expected" ]] || { echo "::error::policy runtime digest mismatch" >&2; exit 1; } - - name: Add Velnor workflow runtime to PATH - shell: bash - env: - EXPECTED_REVISION: be61acbbbfb71db6b51337e547e39de049d4fc15 - run: | - set -euo pipefail - home="$RUNNER_TEMP/velnor-workflow-runtime-artifact" - install -Dm0755 .velnor-workflow-runtime/velnor-workflow "$home/bin/velnor-workflow" - install -Dm0755 .velnor-workflow-runtime/velnor-workflow-policy "$home/bin/velnor-workflow-policy" - expected_closure="$(jq -er '.policy_closure' .velnor-workflow-runtime/manifest.json)" - reported="$("$home/bin/velnor-workflow-policy" --closure)" - [[ "$reported" == "$expected_closure" ]] || { echo "::error::policy runtime reports closure $reported, expected $expected_closure" >&2; exit 1; } - echo "$home/bin" >> "$GITHUB_PATH" - echo "VELNOR_WORKFLOW_PINNED_BINARY=$home/bin/velnor-workflow-policy" >> "$GITHUB_ENV" - - name: Download expected work - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: velnor-expected-work - path: .velnor-ci-expected-work - - name: Download reported unit results - # A no-work plan runs no unit jobs, so zero result artifacts is the - # expected case there — and the aggregate fails a real-work plan with - # zero records anyway. Tolerate the empty download; never the verdict. - continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - pattern: velnor-result-* - merge-multiple: true - path: .velnor-ci-results - - name: Collect reported unit results - shell: bash - run: | - set -euo pipefail - shopt -s nullglob - mkdir -p .velnor-ci-results - files=(.velnor-ci-results/result-*.json) - for file in "${files[@]}"; do - if jq -e 'any(.results[]?; has("reused_from"))' "$file" >/dev/null; then - echo "::error::$file carries reused_from without a validate_reuse decision; render emits no reused results" >&2 - exit 1 - fi - done - if (( ${#files[@]} == 0 )); then - printf '{"results":[]}\n' > .velnor-ci-results.json - else - jq -s '{results: ([.[].results // empty] | add // [])}' "${files[@]}" > .velnor-ci-results.json - fi - echo "collected $(jq '.results | length' .velnor-ci-results.json) reported result(s) from ${#files[@]} record file(s)" - - name: Score expected work against reported results - env: - BASE_SHA: ${{ needs.plan.outputs.base_sha }} - HEAD_SHA: ${{ needs.plan.outputs.head_sha }} - shell: bash - run: | - set -euo pipefail - velnor-workflow aggregate --expected .velnor-ci-expected-work/expected-work.json --results .velnor-ci-results.json - - name: Validate generated stack results - env: - NEEDS_JSON: ${{ toJSON(needs) }} - SELECTED_UNITS: ${{ needs.plan.outputs.units }} - PLAN_DIGEST: ${{ needs.plan.outputs.plan_digest }} - EXCLUDED: ${{ needs.plan.outputs.excluded }} - EXPECTED_CALLERS: "[{\"job_id\":\"github-hosted-reuse\",\"provider\":\"github-hosted\",\"unit_id\":\"reuse\"}]" - PROVIDER_ADMITTED_GITHUB_HOSTED: ${{ true }} - shell: bash - run: | - set -euo pipefail - if [[ -z "$PLAN_DIGEST" ]]; then - echo "plan did not freeze a plan digest: the expected set has no identity" >&2 - exit 1 - fi - echo "verdict binds plan digest $PLAN_DIGEST" - result_for_job() { - jq -r --arg job "$1" '.[$job].result // empty' <<<"$NEEDS_JSON" - } - plan_expects() { - [[ "$(jq -r --arg unit "$1" --arg provider "$2" '[.[] | select(.unit_id == $unit) | .providers[] | select(. == $provider)] | length' <<<"$SELECTED_UNITS")" -gt 0 ]] - } - if ! jq -e --argjson expected "$EXPECTED_CALLERS" '($expected | type == "array") and all($expected[]; type == "object" and (.unit_id | type) == "string" and (.provider | type) == "string" and (.job_id | type) == "string")' -n; then - echo "generated required-caller contract is malformed" >&2 - exit 1 - fi - if ! jq -e --argjson expected "$EXPECTED_CALLERS" 'type == "array" and (map(.unit_id) | unique | length) == length and all(.[]; . as $entry | ($entry | type) == "object" and ($entry.unit_id | type) == "string" and ($entry.unit_id | length) > 0 and ($entry.providers | type) == "array" and ($entry.providers | length) > 0 and (($entry.providers | map(type == "string" and length > 0) | all)) and (($entry.providers | unique | length) == ($entry.providers | length)) and all($entry.providers[]; . as $provider | any($expected[]; .unit_id == $entry.unit_id and .provider == $provider)))' <<<"$SELECTED_UNITS" >/dev/null; then - echo "plan selected-unit output contains an unknown, duplicate, empty, or unmapped obligation" >&2 - exit 1 - fi - if ! jq -e 'type == "object"' <<<"$NEEDS_JSON" >/dev/null; then - echo "workflow needs output is malformed" >&2 - exit 1 - fi - result="$(result_for_job plan)" - if [[ "$result" != success ]]; then - echo "required CI prerequisite plan did not pass: $result" >&2 - exit 1 - fi - if plan_expects "reuse" "github-hosted"; then - result="$(result_for_job github-hosted-reuse)" - if [[ "$PROVIDER_ADMITTED_GITHUB_HOSTED" == true ]]; then - case "$result" in - success) ;; - skipped) echo "expected CI job github-hosted-reuse was skipped: a skipped expected result cannot pass" >&2; exit 1 ;; - cancelled) echo "expected CI job github-hosted-reuse was cancelled: a cancelled expected result cannot pass" >&2; exit 1 ;; - *) echo "expected CI job github-hosted-reuse did not pass: $result" >&2; exit 1 ;; - esac - else - case "$result" in - skipped) ;; - *) echo "selected CI job github-hosted-reuse ran outside its provider admission (PROVIDER_ADMITTED_GITHUB_HOSTED=$PROVIDER_ADMITTED_GITHUB_HOSTED): $result" >&2; exit 1 ;; - esac - fi - else - result="$(result_for_job github-hosted-reuse)" - case "$result" in - skipped) ;; - success) echo "unexpected CI job github-hosted-reuse succeeded outside the expected set: the plan did not declare it" >&2; exit 1 ;; - *) echo "unexpected CI job github-hosted-reuse ran outside the expected set: $result" >&2; exit 1 ;; - esac - fi - required: - name: "Control / Required" - if: ${{ !cancelled() }} - needs: [ci-required] - runs-on: ubuntu-24.04 - timeout-minutes: 5 - steps: - - name: Mirror CI / Required - if: ${{ needs.ci-required.result != 'success' }} - run: exit 1 diff --git a/.github/workflows/ci-unit-docs.yml b/.github/workflows/ci-unit-docs.yml deleted file mode 100644 index 683f977..0000000 --- a/.github/workflows/ci-unit-docs.yml +++ /dev/null @@ -1,375 +0,0 @@ -# Generated by velnor-workflow. Regenerate; do not hand-edit. -name: Documentation -on: - workflow_call: - inputs: - unit: - required: true - type: string - selected_units: - required: true - type: string - selected_unit_ids: - required: true - type: string - scope: - required: true - type: string - full_units: - required: true - type: string - base_sha: - required: true - type: string - head_sha: - required: true - type: string - plan_digest: - required: true - type: string - provider: - required: true - type: string - mise_tools: - required: false - type: string - default: "" - mise_runner: - required: false - type: boolean - default: false - mbx_enabled: - required: false - type: boolean - default: false - mbx_compat: - required: false - type: string - default: "" - mbx_dependency_files: - required: false - type: string - default: "" - mbx_freshness_files: - required: false - type: string - default: "" - cargo_bin_tools: - required: false - type: string - default: "" - tool_version: - required: false - type: string - default: "" - node_cache_dependency_path: - required: false - type: string - default: "" - cache_paths: - required: false - type: string - default: "" - cache_key_files: - required: false - type: string - default: "" - seed_compat: - required: false - type: string - default: "" - seed_dependency_files: - required: false - type: string - default: "" - seed_freshness_files: - required: false - type: string - default: "" - cargo_root: - required: false - type: string - default: "" - cargo_fetch_skip_when_warm: - required: false - type: boolean - default: false - cargo_net_offline: - required: false - type: boolean - default: false - host_warm_layers: - required: false - type: string - default: "" - policy_runtime: - required: false - type: boolean - default: false - candidate_publish: - required: false - type: boolean - default: false - apple_executor: - required: false - type: boolean - default: false - unit_platform: - required: false - type: string - default: "" - unit_trust: - required: false - type: string - default: "" - unit_dependencies: - required: false - type: string - default: "" - unit_admission: - required: false - type: string - default: "" - -jobs: - verify-github-hosted: - name: "GitHub · hosted" - if: ${{ inputs.provider == 'github-hosted' && contains(inputs.selected_units, format('"unit_id":"{0}"', inputs.unit)) && (true) }} - runs-on: ubuntu-24.04 - timeout-minutes: 45 - steps: - - name: Mark CI job start - if: always() - run: | - timing_dir="$RUNNER_TEMP/velnor-ci-timing-${GITHUB_RUN_ID:-unknown}-${GITHUB_RUN_ATTEMPT:-0}-${GITHUB_JOB:-unknown}" - umask 077 - mkdir -p "$timing_dir" - marker="$timing_dir/JOB_STARTED" - if [[ ! -e "$marker" ]]; then - if (set -C; printf '%s\n' "$(date +%s)" > "$marker") 2>/dev/null; then - chmod 0444 "$marker" 2>/dev/null || true - fi - fi - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - ref: ${{ inputs.head_sha }} - - name: Record unit dependencies - env: - UNIT_ID: ${{ inputs.unit }} - UNIT_DEPENDENCIES: ${{ inputs.unit_dependencies }} - UNIT_ADMISSION: ${{ inputs.unit_admission }} - UNIT_PROVIDER: ${{ inputs.provider }} - run: | - { - echo '## Unit dependencies' - echo - echo "- Unit: $UNIT_ID" - echo "- Provider: $UNIT_PROVIDER" - echo "- Admission: $UNIT_ADMISSION" - if [[ -z "$UNIT_DEPENDENCIES" ]]; then - echo '- Dependencies: none' - else - echo "- Dependencies: $UNIT_DEPENDENCIES" - fi - } >> "$GITHUB_STEP_SUMMARY" - - name: Download Velnor workflow runtime - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: velnor-workflow-runtime-be61acbbbfb71db6b51337e547e39de049d4fc15-${{ runner.os }}-${{ runner.arch }} - path: .velnor-workflow-runtime - - name: Verify Velnor workflow runtime - shell: bash - env: - EXPECTED_REVISION: be61acbbbfb71db6b51337e547e39de049d4fc15 - run: | - set -euo pipefail - manifest=.velnor-workflow-runtime/manifest.json - jq -e --arg revision "$EXPECTED_REVISION" --arg repository "$GITHUB_REPOSITORY" --arg platform "${RUNNER_OS}-${RUNNER_ARCH}" --arg run_id "$GITHUB_RUN_ID" '.revision == $revision and .repository == $repository and .platform == $platform and .run_id == $run_id and (.run_id | test("^[0-9]+$")) and .job_id != "" and (.binary_sha256 | test("^[0-9a-f]{64}$")) and (.policy_binary_sha256 | test("^[0-9a-f]{64}$")) and (.closure | test("^[0-9a-f]{64}$")) and (.policy_closure | test("^[0-9a-f]{64}$"))' "$manifest" >/dev/null - expected="$(jq -er '.binary_sha256' "$manifest")" - actual="$(sha256sum .velnor-workflow-runtime/velnor-workflow | awk '{print $1}')" - [[ "$actual" == "$expected" ]] || { echo "::error::runtime digest mismatch" >&2; exit 1; } - expected="$(jq -er '.policy_binary_sha256' "$manifest")" - actual="$(sha256sum .velnor-workflow-runtime/velnor-workflow-policy | awk '{print $1}')" - [[ "$actual" == "$expected" ]] || { echo "::error::policy runtime digest mismatch" >&2; exit 1; } - - name: Add Velnor workflow runtime to PATH - shell: bash - env: - EXPECTED_REVISION: be61acbbbfb71db6b51337e547e39de049d4fc15 - run: | - set -euo pipefail - home="$RUNNER_TEMP/velnor-workflow-runtime-artifact" - install -Dm0755 .velnor-workflow-runtime/velnor-workflow "$home/bin/velnor-workflow" - install -Dm0755 .velnor-workflow-runtime/velnor-workflow-policy "$home/bin/velnor-workflow-policy" - expected_closure="$(jq -er '.policy_closure' .velnor-workflow-runtime/manifest.json)" - reported="$("$home/bin/velnor-workflow-policy" --closure)" - [[ "$reported" == "$expected_closure" ]] || { echo "::error::policy runtime reports closure $reported, expected $expected_closure" >&2; exit 1; } - echo "$home/bin" >> "$GITHUB_PATH" - echo "VELNOR_WORKFLOW_PINNED_BINARY=$home/bin/velnor-workflow-policy" >> "$GITHUB_ENV" - - name: Mark runner setup end - if: always() - run: | - timing_dir="$RUNNER_TEMP/velnor-ci-timing-${GITHUB_RUN_ID:-unknown}-${GITHUB_RUN_ATTEMPT:-0}-${GITHUB_JOB:-unknown}" - umask 077 - mkdir -p "$timing_dir" - marker="$timing_dir/RUNNER_SETUP_ENDED" - if [[ ! -e "$marker" ]]; then - if (set -C; printf '%s\n' "$(date +%s)" > "$marker") 2>/dev/null; then - chmod 0444 "$marker" 2>/dev/null || true - fi - fi - - name: Materialize Velnor CI selection - shell: bash - env: - SELECTION_BASE_SHA: ${{ inputs.base_sha }} - SELECTION_HEAD_SHA: ${{ inputs.head_sha }} - SELECTION_SCOPE: ${{ inputs.scope }} - SELECTION_UNITS: ${{ inputs.selected_unit_ids }} - SELECTION_FULL_UNITS: ${{ inputs.full_units }} - SELECTION_PLAN_DIGEST: ${{ inputs.plan_digest }} - run: | - set -euo pipefail - mkdir -p .velnor-ci-selection - { - printf 'version=2\n' - printf 'base_sha=%s\n' "$SELECTION_BASE_SHA" - printf 'head_sha=%s\n' "$SELECTION_HEAD_SHA" - printf 'scope=%s\n' "$SELECTION_SCOPE" - printf 'units=%s\n' "$SELECTION_UNITS" - printf 'full_units=%s\n' "$SELECTION_FULL_UNITS" - printf 'plan_digest=%s\n' "$SELECTION_PLAN_DIGEST" - } > .velnor-ci-selection/velnor-ci-selection - - name: Mark selection transport end - if: always() - run: | - timing_dir="$RUNNER_TEMP/velnor-ci-timing-${GITHUB_RUN_ID:-unknown}-${GITHUB_RUN_ATTEMPT:-0}-${GITHUB_JOB:-unknown}" - umask 077 - mkdir -p "$timing_dir" - marker="$timing_dir/SELECTION_ENDED" - if [[ ! -e "$marker" ]]; then - if (set -C; printf '%s\n' "$(date +%s)" > "$marker") 2>/dev/null; then - chmod 0444 "$marker" 2>/dev/null || true - fi - fi - - name: Set up Mise tools - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 - with: - install_args: ${{ inputs.mise_tools }} - cache: true - cache_save: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') }} - - name: Mark tool bootstrap end - if: always() - run: | - timing_dir="$RUNNER_TEMP/velnor-ci-timing-${GITHUB_RUN_ID:-unknown}-${GITHUB_RUN_ATTEMPT:-0}-${GITHUB_JOB:-unknown}" - umask 077 - mkdir -p "$timing_dir" - marker="$timing_dir/TOOL_BOOTSTRAP_ENDED" - if [[ ! -e "$marker" ]]; then - if (set -C; printf '%s\n' "$(date +%s)" > "$marker") 2>/dev/null; then - chmod 0444 "$marker" 2>/dev/null || true - fi - fi - - name: Mark cache prep end - if: always() - run: | - timing_dir="$RUNNER_TEMP/velnor-ci-timing-${GITHUB_RUN_ID:-unknown}-${GITHUB_RUN_ATTEMPT:-0}-${GITHUB_JOB:-unknown}" - umask 077 - mkdir -p "$timing_dir" - marker="$timing_dir/CACHE_PREP_ENDED" - if [[ ! -e "$marker" ]]; then - if (set -C; printf '%s\n' "$(date +%s)" > "$marker") 2>/dev/null; then - chmod 0444 "$marker" 2>/dev/null || true - fi - fi - - name: Mark cargo fetch end - if: always() - run: | - timing_dir="$RUNNER_TEMP/velnor-ci-timing-${GITHUB_RUN_ID:-unknown}-${GITHUB_RUN_ATTEMPT:-0}-${GITHUB_JOB:-unknown}" - umask 077 - mkdir -p "$timing_dir" - marker="$timing_dir/CARGO_FETCH_ENDED" - if [[ ! -e "$marker" ]]; then - if (set -C; printf '%s\n' "$(date +%s)" > "$marker") 2>/dev/null; then - chmod 0444 "$marker" 2>/dev/null || true - fi - fi - - name: Run unit checks - env: - CI_SCOPE: ${{ inputs.scope }} - CI_UNIT_ID: ${{ inputs.unit }} - EVENT_NAME: ${{ github.event_name }} - BASE_SHA: ${{ inputs.base_sha }} - HEAD_SHA: ${{ inputs.head_sha }} - VELNOR_SELECTION_FILE: .velnor-ci-selection/velnor-ci-selection - MISE_AUTO_INSTALL: "false" - MISE_EXEC_AUTO_INSTALL: "false" - MISE_NOT_FOUND_AUTO_INSTALL: "false" - run: | - set -o pipefail - timing_dir="$RUNNER_TEMP/velnor-ci-timing-${GITHUB_RUN_ID:-unknown}-${GITHUB_RUN_ATTEMPT:-0}-${GITHUB_JOB:-unknown}" - umask 077 - mkdir -p "$timing_dir" - marker="$timing_dir/CHECKS_STARTED" - if [[ ! -e "$marker" ]]; then - if (set -C; printf '%s\n' "$(date +%s)" > "$marker") 2>/dev/null; then - chmod 0444 "$marker" 2>/dev/null || true - fi - fi - rc=0 - velnor-workflow run --config .github/ci/project.toml --scope "$CI_SCOPE" --unit "$CI_UNIT_ID" 2>&1 | tee "$RUNNER_TEMP/velnor-unit-log.txt" || rc=$? - timing_dir="$RUNNER_TEMP/velnor-ci-timing-${GITHUB_RUN_ID:-unknown}-${GITHUB_RUN_ATTEMPT:-0}-${GITHUB_JOB:-unknown}" - umask 077 - mkdir -p "$timing_dir" - marker="$timing_dir/CHECKS_ENDED" - if [[ ! -e "$marker" ]]; then - if (set -C; printf '%s\n' "$(date +%s)" > "$marker") 2>/dev/null; then - chmod 0444 "$marker" 2>/dev/null || true - fi - fi - exit $rc - - name: Mark cleanup end - if: always() - run: | - timing_dir="$RUNNER_TEMP/velnor-ci-timing-${GITHUB_RUN_ID:-unknown}-${GITHUB_RUN_ATTEMPT:-0}-${GITHUB_JOB:-unknown}" - umask 077 - mkdir -p "$timing_dir" - marker="$timing_dir/CLEANUP_ENDED" - if [[ ! -e "$marker" ]]; then - if (set -C; printf '%s\n' "$(date +%s)" > "$marker") 2>/dev/null; then - chmod 0444 "$marker" 2>/dev/null || true - fi - fi - - name: Report phase timings and cache outcomes - if: always() - uses: tailrocks/velnor/.github/actions/report-velnor-ci-outcomes@be61acbbbfb71db6b51337e547e39de049d4fc15 - with: - job_label: ${{ inputs.unit }} - ci_lane: github - cache_declared_layers: - - name: Record unit result - if: ${{ always() }} - env: - VELNOR_RESULT_UNIT: ${{ inputs.unit }} - VELNOR_RESULT_LANE: github-hosted - VELNOR_RESULT_OUTCOME: ${{ job.status }} - shell: bash - run: | - set -euo pipefail - case "$VELNOR_RESULT_OUTCOME" in - success) outcome=success ;; - cancelled) outcome=cancelled ;; - *) outcome=failure ;; - esac - mkdir -p .velnor-ci-results - jq -n --arg unit "$VELNOR_RESULT_UNIT" --arg lane "$VELNOR_RESULT_LANE" --arg outcome "$outcome" '{results: [{unit: $unit, lane: $lane, outcome: $outcome}]}' > ".velnor-ci-results/result-$VELNOR_RESULT_UNIT-$VELNOR_RESULT_LANE.json" - - name: Upload unit result - if: ${{ always() }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: velnor-result-${{ inputs.unit }}-github-hosted - path: .velnor-ci-results/result-${{ inputs.unit }}-github-hosted.json - if-no-files-found: error - overwrite: true - retention-days: 7 - diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9b0a9db --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,300 @@ +# Generated by Velnor Actions 0.1.0; edit .velnor/config.toml and regenerate. +name: CI +"on": + pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review + push: + branches: + - main + merge_group: +permissions: + contents: read + actions: read +concurrency: + group: velnor-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} +jobs: + actionlint: + name: Actionlint + runs-on: ubuntu-26.04 + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + persist-credentials: "false" + - name: Setup Mise + uses: jdx/mise-action@9149ea85001c7435d5a66bb127d6a1b6227cb0a5 + with: + cache: "true" + cache_key: mise-v1-x86_64-unknown-linux-gnu-2026.9.18-75c77caa4018556f + cache_save: "false" + env: "false" + install: "false" + sha256: d24fe0bf7e613824ad99f7b8dac3f2b381a37b9f75f84dd250855217095a8de4 + version: 2026.9.18 + - name: Run actionlint + run: mise --no-config --no-env --no-hooks exec actionlint@1.7.12 shellcheck@0.11.0 -- actionlint -color + - name: Save Mise tools + if: success() && github.event_name == 'push' + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 + with: + key: mise-v1-x86_64-unknown-linux-gnu-2026.9.18-75c77caa4018556f + path: ~/.local/share/mise + plan: + name: Plan + runs-on: ubuntu-26.04 + timeout-minutes: 10 + outputs: + covered_tasks: ${{ steps.plan.outputs.covered_tasks }} + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + fetch-depth: "0" + persist-credentials: "false" + - name: Setup Mise + uses: jdx/mise-action@9149ea85001c7435d5a66bb127d6a1b6227cb0a5 + with: + cache: "true" + cache_key: mise-v1-x86_64-unknown-linux-gnu-2026.9.18-761a279144527661 + cache_save: "false" + env: "false" + install: "false" + sha256: d24fe0bf7e613824ad99f7b8dac3f2b381a37b9f75f84dd250855217095a8de4 + version: 2026.9.18 + - name: Prepare pinned tools + env: + MISE_CARGO_HOME: ${{ runner.temp }}/velnor/cargo + MISE_LOCKFILE: "0" + MISE_NO_CONFIG: "1" + MISE_NO_ENV: "1" + MISE_NO_HOOKS: "1" + MISE_RUSTUP_HOME: ${{ runner.temp }}/velnor/rustup + RUSTUP_TOOLCHAIN: 1.98.1 + run: mise --no-config --no-env --no-hooks install rust@1.98.1 actionlint@1.7.12 shellcheck@0.11.0 zizmor@1.30.1 + - name: Prepare Rust components + env: + MISE_AUTO_INSTALL: "false" + MISE_CARGO_HOME: ${{ runner.temp }}/velnor/cargo + MISE_EXEC_AUTO_INSTALL: "false" + MISE_LOCKFILE: "0" + MISE_NO_CONFIG: "1" + MISE_NO_ENV: "1" + MISE_NO_HOOKS: "1" + MISE_RUSTUP_HOME: ${{ runner.temp }}/velnor/rustup + RUSTUP_TOOLCHAIN: 1.98.1 + run: mise --no-config --no-env --no-hooks exec rust@1.98.1 -- rustup component add --toolchain 1.98.1-x86_64-unknown-linux-gnu clippy rustfmt + - name: Acquire Velnor + env: + ACTIONS_ID_TOKEN_REQUEST_TOKEN: "" + ACTIONS_ID_TOKEN_REQUEST_URL: "" + ACTIONS_RUNTIME_TOKEN: "" + CARGO_REGISTRY_TOKEN: "" + GH_CONFIG_DIR: "" + GH_HOST: "" + GH_TOKEN: "" + GITHUB_TOKEN: "" + MISE_GITHUB_TOKEN: "" + NODE_AUTH_TOKEN: "" + NPM_TOKEN: "" + VELNOR_ASSET_SHA256: aa7e44d6579e9c586106d120ed3658fcf1c9b041027ad9f03473e8efacd3b5d5 + VELNOR_ASSET_URL: https://github.com/tailrocks/velnor-new/releases/download/v0.1.0/velnor-actions-0.1.0-x86_64-unknown-linux-gnu + VELNOR_RELEASE_COMMIT: c57c700459bbe1549fe7eedcb7d8689585c38986 + run: "sh -c 'unset ACTIONS_ID_TOKEN_REQUEST_TOKEN ACTIONS_ID_TOKEN_REQUEST_URL ACTIONS_RUNTIME_TOKEN GITHUB_TOKEN MISE_GITHUB_TOKEN GH_TOKEN GH_HOST GH_CONFIG_DIR; mkdir -p \"$RUNNER_TEMP/velnor/bin\" && curl -fsSL --proto '\\''=https'\\'' --tlsv1.2 \"$VELNOR_ASSET_URL\" -o \"$RUNNER_TEMP/velnor/bin/velnor-actions-0.1.0\" && echo \"$VELNOR_ASSET_SHA256 $RUNNER_TEMP/velnor/bin/velnor-actions-0.1.0\" | sha256sum -c - && chmod +x \"$RUNNER_TEMP/velnor/bin/velnor-actions-0.1.0\"'" + - name: Write request + env: + VELNOR_INTERNAL_OP: write-request-v1 + VELNOR_REQUEST_FILE: ${{ runner.temp }}/velnor/request/plan-v1-request.json + run: "\"$RUNNER_TEMP/velnor/bin/velnor-actions-0.1.0\"" + - name: Check generated files + env: + ACTIONS_ID_TOKEN_REQUEST_TOKEN: "" + ACTIONS_ID_TOKEN_REQUEST_URL: "" + ACTIONS_RUNTIME_TOKEN: "" + CARGO_REGISTRY_TOKEN: "" + GH_CONFIG_DIR: "" + GH_HOST: "" + GH_TOKEN: "" + GITHUB_TOKEN: "" + MISE_AUTO_INSTALL: "false" + MISE_CARGO_HOME: ${{ runner.temp }}/velnor/cargo + MISE_EXEC_AUTO_INSTALL: "false" + MISE_GITHUB_TOKEN: "" + MISE_LOCKFILE: "0" + MISE_NO_CONFIG: "1" + MISE_NO_ENV: "1" + MISE_NO_HOOKS: "1" + MISE_RUSTUP_HOME: ${{ runner.temp }}/velnor/rustup + NODE_AUTH_TOKEN: "" + NPM_TOKEN: "" + RUSTUP_TOOLCHAIN: 1.98.1 + run: "sh -c 'unset ACTIONS_ID_TOKEN_REQUEST_TOKEN ACTIONS_ID_TOKEN_REQUEST_URL ACTIONS_RUNTIME_TOKEN GITHUB_TOKEN MISE_GITHUB_TOKEN GH_TOKEN GH_HOST GH_CONFIG_DIR; $RUNNER_TEMP/velnor/bin/velnor-actions-0.1.0 generate --output-dir \"$RUNNER_TEMP/velnor-actions-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}\" && diff -r --brief .github \"$RUNNER_TEMP/velnor-actions-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}/.github\"'" + - name: Plan + id: plan + env: + VELNOR_INTERNAL_OP: plan-v1 + VELNOR_REQUEST_FILE: ${{ runner.temp }}/velnor/request/plan-v1-request.json + MISE_AUTO_INSTALL: "false" + MISE_CARGO_HOME: ${{ runner.temp }}/velnor/cargo + MISE_EXEC_AUTO_INSTALL: "false" + MISE_LOCKFILE: "0" + MISE_NO_CONFIG: "1" + MISE_NO_ENV: "1" + MISE_NO_HOOKS: "1" + MISE_RUSTUP_HOME: ${{ runner.temp }}/velnor/rustup + RUSTUP_TOOLCHAIN: 1.98.1 + run: "\"$RUNNER_TEMP/velnor/bin/velnor-actions-0.1.0\"" + - name: Publish plan + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + if-no-files-found: error + name: velnor-plan-r${{ github.run_id }}-a${{ github.run_attempt }} + path: ${{ runner.temp }}/velnor/r${{ github.run_id }}-a${{ github.run_attempt }} + retention-days: "30" + - name: Save Mise tools + if: success() && github.event_name == 'push' + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 + with: + key: mise-v1-x86_64-unknown-linux-gnu-2026.9.18-761a279144527661 + path: ~/.local/share/mise + publish-baseline: + name: Publish baseline + runs-on: ubuntu-26.04 + timeout-minutes: 10 + needs: + - required + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + steps: + - name: Acquire Velnor + env: + ACTIONS_ID_TOKEN_REQUEST_TOKEN: "" + ACTIONS_ID_TOKEN_REQUEST_URL: "" + ACTIONS_RUNTIME_TOKEN: "" + CARGO_REGISTRY_TOKEN: "" + GH_CONFIG_DIR: "" + GH_HOST: "" + GH_TOKEN: "" + GITHUB_TOKEN: "" + MISE_GITHUB_TOKEN: "" + NODE_AUTH_TOKEN: "" + NPM_TOKEN: "" + VELNOR_ASSET_SHA256: aa7e44d6579e9c586106d120ed3658fcf1c9b041027ad9f03473e8efacd3b5d5 + VELNOR_ASSET_URL: https://github.com/tailrocks/velnor-new/releases/download/v0.1.0/velnor-actions-0.1.0-x86_64-unknown-linux-gnu + VELNOR_RELEASE_COMMIT: c57c700459bbe1549fe7eedcb7d8689585c38986 + run: "sh -c 'unset ACTIONS_ID_TOKEN_REQUEST_TOKEN ACTIONS_ID_TOKEN_REQUEST_URL ACTIONS_RUNTIME_TOKEN GITHUB_TOKEN MISE_GITHUB_TOKEN GH_TOKEN GH_HOST GH_CONFIG_DIR; mkdir -p \"$RUNNER_TEMP/velnor/bin\" && curl -fsSL --proto '\\''=https'\\'' --tlsv1.2 \"$VELNOR_ASSET_URL\" -o \"$RUNNER_TEMP/velnor/bin/velnor-actions-0.1.0\" && echo \"$VELNOR_ASSET_SHA256 $RUNNER_TEMP/velnor/bin/velnor-actions-0.1.0\" | sha256sum -c - && chmod +x \"$RUNNER_TEMP/velnor/bin/velnor-actions-0.1.0\"'" + - name: Download plan + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + name: velnor-plan-r${{ github.run_id }}-a${{ github.run_attempt }} + path: ${{ runner.temp }}/velnor/r${{ github.run_id }}-a${{ github.run_attempt }} + - name: Write request + env: + VELNOR_INTERNAL_OP: write-request-v1 + VELNOR_REQUEST_FILE: ${{ runner.temp }}/velnor/request/publish-baseline-v1-request.json + run: "\"$RUNNER_TEMP/velnor/bin/velnor-actions-0.1.0\"" + - name: Publish baseline + id: publish-baseline + env: + VELNOR_INTERNAL_OP: publish-baseline-v1 + VELNOR_REQUEST_FILE: ${{ runner.temp }}/velnor/request/publish-baseline-v1-request.json + run: "\"$RUNNER_TEMP/velnor/bin/velnor-actions-0.1.0\"" + - name: Upload baseline + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + if-no-files-found: error + name: ${{ steps.publish-baseline.outputs.artifact_name }} + path: ${{ runner.temp }}/velnor/r${{ github.run_id }}-a${{ github.run_attempt }}/baseline.json + retention-days: "90" + required: + name: Required + runs-on: ubuntu-26.04 + timeout-minutes: 10 + needs: + - plan + - actionlint + if: always() + steps: + - name: Setup Mise + uses: jdx/mise-action@9149ea85001c7435d5a66bb127d6a1b6227cb0a5 + with: + cache: "true" + cache_key: mise-v1-x86_64-unknown-linux-gnu-2026.9.18-14ee3a9bbb90604c + cache_save: "false" + env: "false" + install: "false" + sha256: d24fe0bf7e613824ad99f7b8dac3f2b381a37b9f75f84dd250855217095a8de4 + version: 2026.9.18 + - name: Acquire Velnor + env: + ACTIONS_ID_TOKEN_REQUEST_TOKEN: "" + ACTIONS_ID_TOKEN_REQUEST_URL: "" + ACTIONS_RUNTIME_TOKEN: "" + CARGO_REGISTRY_TOKEN: "" + GH_CONFIG_DIR: "" + GH_HOST: "" + GH_TOKEN: "" + GITHUB_TOKEN: "" + MISE_GITHUB_TOKEN: "" + NODE_AUTH_TOKEN: "" + NPM_TOKEN: "" + VELNOR_ASSET_SHA256: aa7e44d6579e9c586106d120ed3658fcf1c9b041027ad9f03473e8efacd3b5d5 + VELNOR_ASSET_URL: https://github.com/tailrocks/velnor-new/releases/download/v0.1.0/velnor-actions-0.1.0-x86_64-unknown-linux-gnu + VELNOR_RELEASE_COMMIT: c57c700459bbe1549fe7eedcb7d8689585c38986 + run: "sh -c 'unset ACTIONS_ID_TOKEN_REQUEST_TOKEN ACTIONS_ID_TOKEN_REQUEST_URL ACTIONS_RUNTIME_TOKEN GITHUB_TOKEN MISE_GITHUB_TOKEN GH_TOKEN GH_HOST GH_CONFIG_DIR; mkdir -p \"$RUNNER_TEMP/velnor/bin\" && curl -fsSL --proto '\\''=https'\\'' --tlsv1.2 \"$VELNOR_ASSET_URL\" -o \"$RUNNER_TEMP/velnor/bin/velnor-actions-0.1.0\" && echo \"$VELNOR_ASSET_SHA256 $RUNNER_TEMP/velnor/bin/velnor-actions-0.1.0\" | sha256sum -c - && chmod +x \"$RUNNER_TEMP/velnor/bin/velnor-actions-0.1.0\"'" + - name: Prepare pinned tools + env: + MISE_CARGO_HOME: ${{ runner.temp }}/velnor/cargo + MISE_LOCKFILE: "0" + MISE_NO_CONFIG: "1" + MISE_NO_ENV: "1" + MISE_NO_HOOKS: "1" + MISE_RUSTUP_HOME: ${{ runner.temp }}/velnor/rustup + RUSTUP_TOOLCHAIN: 1.98.1 + run: mise --no-config --no-env --no-hooks install gh@2.102.0 + - name: Download plan + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + name: velnor-plan-r${{ github.run_id }}-a${{ github.run_attempt }} + path: ${{ runner.temp }}/velnor/r${{ github.run_id }}-a${{ github.run_attempt }} + - name: Download every expected matrix artifact + env: + GH_REPO: ${{ github.repository }} + GH_TOKEN: ${{ github.token }} + VELNOR_INTERNAL_OP: fetch-reports-v1 + run: "\"$RUNNER_TEMP/velnor/bin/velnor-actions-0.1.0\"" + - name: Write request + env: + VELNOR_INTERNAL_OP: write-request-v1 + VELNOR_NEEDS_JSON: ${{ toJSON(needs) }} + VELNOR_NEEDS_EXPECTED: "[\"actionlint\",\"plan\"]" + VELNOR_REQUEST_FILE: ${{ runner.temp }}/velnor/request/merge-v1-request.json + run: "\"$RUNNER_TEMP/velnor/bin/velnor-actions-0.1.0\"" + - name: Merge reports + env: + VELNOR_INTERNAL_OP: merge-v1 + VELNOR_NEEDS_JSON: ${{ toJSON(needs) }} + VELNOR_NEEDS_EXPECTED: "[\"actionlint\",\"plan\"]" + VELNOR_REQUEST_FILE: ${{ runner.temp }}/velnor/request/merge-v1-request.json + run: "\"$RUNNER_TEMP/velnor/bin/velnor-actions-0.1.0\"" + - name: Save Mise tools + if: success() && github.event_name == 'push' + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 + with: + key: mise-v1-x86_64-unknown-linux-gnu-2026.9.18-14ee3a9bbb90604c + path: ~/.local/share/mise + - name: Publish final report + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + if-no-files-found: error + name: velnor-final-r${{ github.run_id }}-a${{ github.run_attempt }} + path: ${{ runner.temp }}/velnor/r${{ github.run_id }}-a${{ github.run_attempt }}/final-report.json + retention-days: "30" diff --git a/.github/workflows/maintenance.yml b/.github/workflows/maintenance.yml deleted file mode 100644 index daefa02..0000000 --- a/.github/workflows/maintenance.yml +++ /dev/null @@ -1,352 +0,0 @@ -# Generated by velnor-workflow. Regenerate; do not hand-edit. -name: Maintenance -run-name: Maintenance · ${{ github.event_name }} - -on: - pull_request: - types: [closed] - schedule: - - cron: "31 3 * * *" - workflow_dispatch: - inputs: - pull_request_number: - description: Optional closed PR number whose merge cache should be removed - required: false - type: string - -permissions: - actions: write - contents: read - -concurrency: - group: maintenance-${{ github.repository }}-${{ github.event.pull_request.number || inputs.pull_request_number || github.run_id }} - cancel-in-progress: false - -jobs: - prune-pr-cache: - name: Prune closed-PR cache - if: ${{ github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && inputs.pull_request_number != '') }} - runs-on: ubuntu-24.04 - timeout-minutes: 15 - steps: - - name: Delete merge-ref cache namespace - env: - GH_TOKEN: ${{ github.token }} - PR_NUMBER: ${{ github.event.pull_request.number || inputs.pull_request_number }} - run: | - set -euo pipefail - delete_cache_id() { - local id="$1" error attempt - for attempt in 1 2 3; do - error="$(gh api --method DELETE "repos/$GITHUB_REPOSITORY/actions/caches/$id" 2>&1 >/dev/null)" && return 0 - if grep -qi 'not found' <<<"$error"; then - return 0 - fi - if grep -Eq 'HTTP 40[13]' <<<"$error"; then - echo "::error::cache delete refused for id $id ($error); refusing to retry an authorization failure" >&2 - return 2 - fi - if (( attempt < 3 )); then - sleep $((attempt * 2)) - fi - done - echo "::error::failed to delete cache id $id after bounded retries" >&2 - return 1 - } - ref="refs/pull/$PR_NUMBER/merge" - encoded="$(printf '%s' "$ref" | jq -sRr @uri)" - listing="$(gh api --paginate "repos/$GITHUB_REPOSITORY/actions/caches?ref=$encoded" --jq '.actions_caches[].id')" || { - echo "::error::failed to list cache entries for $ref" >&2 - exit 1 - } - if [[ -z "$listing" ]]; then - echo "No merge-ref cache entries found for $ref" - exit 0 - fi - mapfile -t cache_ids <<<"$listing" - deleted=0 - failed=0 - for id in "${cache_ids[@]}"; do - [[ -z "$id" ]] && continue - if (( deleted + failed >= 500 )); then - echo "::error::maintenance delete bound reached (500 cache deletes); rerun maintenance to continue" >&2 - exit 1 - fi - if delete_cache_id "$id"; then - deleted=$((deleted + 1)) - else - status=$? - if (( status == 2 )); then - exit 1 - fi - failed=$((failed + 1)) - fi - done - if (( failed > 0 )); then - echo "::error::$failed closed-PR cache entries could not be deleted; rerun maintenance" >&2 - exit 1 - fi - cache-budget: - name: Cache retention - if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} - runs-on: ubuntu-24.04 - timeout-minutes: 10 - permissions: - contents: read - actions: write - pull-requests: read - steps: - - name: Skip while CI producers are running - id: retention-gate - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - for workflow in ci-main.yml nightly.yml; do - if [[ "$(gh run list --repo "$GITHUB_REPOSITORY" --workflow "$workflow" --status in_progress --limit 1 --json databaseId --jq 'length')" != "0" ]]; then - echo "skip=true" >> "$GITHUB_OUTPUT" - echo "$workflow is in_progress; skipping cache retention" >> "$GITHUB_STEP_SUMMARY" - exit 0 - fi - done - echo "skip=false" >> "$GITHUB_OUTPUT" - - name: Set up Velnor workflow runtime - id: runtime - uses: tailrocks/velnor/.github/actions/setup-velnor-workflow@be61acbbbfb71db6b51337e547e39de049d4fc15 - with: - rev: be61acbbbfb71db6b51337e547e39de049d4fc15 - - name: Set trusted workflow policy revision - if: steps.retention-gate.outputs.skip != 'true' - run: echo "VELNOR_WORKFLOW_POLICY_REVISION=be61acbbbfb71db6b51337e547e39de049d4fc15" >> "$GITHUB_ENV" - - name: Sweep closed-PR merge-ref caches - if: steps.retention-gate.outputs.skip != 'true' - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - delete_cache_id() { - local id="$1" error attempt - for attempt in 1 2 3; do - error="$(gh api --method DELETE "repos/$GITHUB_REPOSITORY/actions/caches/$id" 2>&1 >/dev/null)" && return 0 - if grep -qi 'not found' <<<"$error"; then - return 0 - fi - if grep -Eq 'HTTP 40[13]' <<<"$error"; then - echo "::error::cache delete refused for id $id ($error); refusing to retry an authorization failure" >&2 - return 2 - fi - if (( attempt < 3 )); then - sleep $((attempt * 2)) - fi - done - echo "::error::failed to delete cache id $id after bounded retries" >&2 - return 1 - } - failed=0 - processed=0 - all_refs="$(gh api --paginate "repos/$GITHUB_REPOSITORY/actions/caches?per_page=100" \ - --jq '.actions_caches[].ref')" || { - echo "::error::failed to list cache scopes" >&2 - exit 1 - } - mapfile -t refs < <(printf '%s\n' "$all_refs" | grep -E '^refs/pull/[0-9]+/merge$' | sort -u) - if ((${#refs[@]} == 0)); then - echo "No merge-ref cache scopes found" - exit 0 - fi - for ref in "${refs[@]}"; do - [[ -z "$ref" ]] && continue - pr="${ref#refs/pull/}" - pr="${pr%/merge}" - state="$(gh pr view "$pr" --json state --jq .state 2>/dev/null || echo unknown)" - if [[ "$state" != "CLOSED" ]]; then - continue - fi - encoded="$(printf '%s' "$ref" | jq -sRr @uri)" - listing="$(gh api --paginate "repos/$GITHUB_REPOSITORY/actions/caches?ref=$encoded" \ - --jq '.actions_caches[].id')" || { - echo "::error::failed to list cache entries for $ref" >&2 - exit 1 - } - if [[ -z "$listing" ]]; then - continue - fi - mapfile -t cache_ids <<<"$listing" - echo "Sweeping $ref ($pr): ${#cache_ids[@]} entries" - for id in "${cache_ids[@]}"; do - [[ -z "$id" ]] && continue - if (( processed >= 500 )); then - echo "::error::maintenance delete bound reached (500 cache deletes); rerun maintenance to continue" >&2 - exit 1 - fi - if delete_cache_id "$id"; then - : - else - status=$? - if (( status == 2 )); then - exit 1 - fi - failed=$((failed + 1)) - fi - processed=$((processed + 1)) - done - done - if (( failed > 0 )); then - echo "::error::$failed closed-PR merge-ref cache entries could not be deleted; rerun maintenance" >&2 - exit 1 - fi - - name: Collect Actions cache account - if: steps.retention-gate.outputs.skip != 'true' - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - mkdir -p "$RUNNER_TEMP/cache-retention" - gh api --paginate "repos/$GITHUB_REPOSITORY/actions/caches?per_page=100" \ - --jq '.actions_caches[] | {id, key, size_in_bytes, created_at, last_accessed_at}' \ - > "$RUNNER_TEMP/cache-retention/entries.jsonl" - jq -s '.' "$RUNNER_TEMP/cache-retention/entries.jsonl" \ - > "$RUNNER_TEMP/cache-retention/entries.json" - velnor-workflow cache-plan --mode=budget --entries "$RUNNER_TEMP/cache-retention/entries.json" \ - > "$RUNNER_TEMP/cache-retention/budget.json" - # `gh api --paginate --jq` runs the filter once per page and - # concatenates the outputs: summing inside the filter prints one - # number per page, and the total silently understates the account. - # Slurp the page stream first, then take one total over every entry. - total="$(jq '.total_held_bytes' "$RUNNER_TEMP/cache-retention/budget.json")" - count="$(jq 'length' "$RUNNER_TEMP/cache-retention/entries.json")" - headroom="$(jq '.headroom_bytes' "$RUNNER_TEMP/cache-retention/budget.json")" - if (( headroom >= 0 && headroom < 536870912 )); then - echo "::warning::Actions cache headroom below 512 MiB ($headroom bytes remaining)" >&2 - fi - jq -n --argjson total "$total" --argjson count "$count" --argjson headroom "$headroom" \ - --slurpfile budget "$RUNNER_TEMP/cache-retention/budget.json" \ - --arg captured_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ - '{captured_at: $captured_at, cache_count: $count, total_bytes: $total, headroom_bytes: $headroom, classes: $budget[0].classes}' \ - > "$RUNNER_TEMP/cache-retention/summary.json" - { - cat "$RUNNER_TEMP/cache-retention/summary.json" - echo "Per-class totals:" - jq -r '.classes[] | " \(.id): \(.entry_count) entries, \(.held_bytes) bytes (budget \(.budget_bytes))"' \ - "$RUNNER_TEMP/cache-retention/budget.json" - } >> "$GITHUB_STEP_SUMMARY" - - name: Plan retention evictions - if: steps.retention-gate.outputs.skip != 'true' - run: | - set -euo pipefail - # The plan is the generator's own retention policy, executed - never - # a shell copy of it: per-class budgets, bounded generations per - # variant class, and the protected classes (toolchain seeds, Cargo - # source bundles, the Docker seed baseline) reserved before rolling - # compiler snapshots are touched. An access timestamp is not a - # lease. The newest generation of a variant stays out of reach - # inside the producer window; a superseded generation of the same - # variant is eligible, because the newer save is the producer - # signal that the older entry is no longer being written. - velnor-workflow cache-plan \ - --now "$(date -u +%s)" \ - --entries "$RUNNER_TEMP/cache-retention/entries.json" \ - > "$RUNNER_TEMP/cache-retention/plan.json" - if jq -e 'length > 0' "$RUNNER_TEMP/cache-retention/plan.json" > /dev/null; then - { - echo "Retention plan (evict oldest first: bound, class budget, global budget):" - jq -r 'sort_by(.class, .reason) | group_by(.class, .reason)[] | " \(.[0].class) / \(.[0].reason): \(length) entries, \(map(.size_in_bytes) | add) bytes"' \ - "$RUNNER_TEMP/cache-retention/plan.json" - } >> "$GITHUB_STEP_SUMMARY" - else - echo "Retention plan: nothing to evict" >> "$GITHUB_STEP_SUMMARY" - fi - - name: Apply retention evictions - if: steps.retention-gate.outputs.skip != 'true' - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - delete_cache_id() { - local id="$1" error attempt - for attempt in 1 2 3; do - error="$(gh api --method DELETE "repos/$GITHUB_REPOSITORY/actions/caches/$id" 2>&1 >/dev/null)" && return 0 - if grep -qi 'not found' <<<"$error"; then - return 0 - fi - if grep -Eq 'HTTP 40[13]' <<<"$error"; then - echo "::error::cache delete refused for id $id ($error); refusing to retry an authorization failure" >&2 - return 2 - fi - if (( attempt < 3 )); then - sleep $((attempt * 2)) - fi - done - echo "::error::failed to delete cache id $id after bounded retries" >&2 - return 1 - } - evicted=0 - freed=0 - failed=0 - # The plan is applied verbatim, in its own order: generations beyond - # their class bound first, then classes over budget, then the global - # sweep - which never touches a protected class. The per-run delete - # bound stops the sweep instead of letting one run empty the - # account; the next run continues where this one stopped. - while IFS=$'\t' read -r class reason id size key; do - if (( evicted + failed >= 500 )); then - echo "::error::maintenance delete bound reached (500 cache deletes); rerun maintenance to continue" >&2 - exit 1 - fi - if delete_cache_id "$id"; then - evicted=$((evicted + 1)) - freed=$((freed + size)) - echo "evicted id=$id class=$class reason=$reason size=$size key=$key" - else - status=$? - if (( status == 2 )); then - exit 1 - fi - failed=$((failed + 1)) - echo "::warning::failed to evict cache id $id (class $class, key $key)" >&2 - fi - done < <(jq -r '.[] | [.class, .reason, .id, .size_in_bytes, .key] | @tsv' \ - "$RUNNER_TEMP/cache-retention/plan.json") - # Every eviction is recorded under its cache class and its reason, - # so a later cold run can be correlated with the eviction that - # caused it. - { - echo "Evictions by cache class:" - jq -r 'group_by(.class)[] | " \(.[0].class): \(length) evictions, \(map(.size_in_bytes) | add) bytes"' \ - "$RUNNER_TEMP/cache-retention/plan.json" - } >> "$GITHUB_STEP_SUMMARY" - jq -n --argjson evicted "$evicted" --argjson freed "$freed" --argjson failed "$failed" \ - '{evicted_caches: $evicted, failed_evictions: $failed, freed_bytes: $freed}' \ - >> "$GITHUB_STEP_SUMMARY" - # A failed eviction is a loud failure, never a swallowed warning: - # silent DELETE failures leave the account over budget while the - # run reports success. - if (( failed > 0 )); then - echo "::error::$failed retention evictions failed; rerun maintenance" >&2 - exit 1 - fi - - name: Publish retention evidence - if: steps.retention-gate.outputs.skip != 'true' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: cache-retention-${{ github.run_id }} - path: ${{ runner.temp }}/cache-retention - if-no-files-found: error - retention-days: 14 - - name: Enforce cache budget - if: steps.retention-gate.outputs.skip != 'true' - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - # Re-query live state: the enforcement decision must reflect what the - # account actually holds after eviction, not the pre-eviction - # snapshot. The same page-streaming rule as collection applies: the - # sum is taken after slurping every page. - total="$(gh api --paginate "repos/$GITHUB_REPOSITORY/actions/caches?per_page=100" \ - --jq '.actions_caches[].size_in_bytes' | jq -s 'add // 0')" - budget="$(velnor-workflow cache-plan --mode=budget)" - if (( total > budget )); then - echo "::error::Actions cache account exceeds budget: $total > $budget bytes" >&2 - exit 1 - fi diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml deleted file mode 100644 index 41cf3fa..0000000 --- a/.github/workflows/nightly.yml +++ /dev/null @@ -1,103 +0,0 @@ -# Generated by velnor-workflow. Regenerate; do not hand-edit. -name: Nightly -run-name: Nightly · ${{ github.event_name }} · ${{ github.ref_name }} - -on: - schedule: - - cron: '17 3 * * *' - workflow_dispatch: - inputs: - scope: - description: Verification scope - required: true - default: full - type: choice - options: - - affected - - full - base_sha: - description: Git ref or SHA used as the affected-selection base - required: false - default: refs/heads/main - type: string - simulate_failure: - description: Force the red-to-signal test path - required: false - default: false - type: boolean - - -concurrency: - group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: false - -permissions: - actions: read - contents: read - -jobs: - dispatch-ci-main: - name: "Control / Dispatch ci-main" - if: ${{ github.event_name != 'workflow_dispatch' || !inputs.simulate_failure }} - runs-on: ubuntu-24.04 - timeout-minutes: 5 - permissions: - actions: write - contents: read - steps: - - name: Dispatch ci-main on default branch - env: - GH_TOKEN: ${{ github.token }} - GITHUB_REPOSITORY: ${{ github.repository }} - DEFAULT_BRANCH: main - DISPATCH_SCOPE: ${{ github.event.inputs.scope || 'full' }} - DISPATCH_BASE_SHA: ${{ github.event.inputs.base_sha || format('refs/heads/{0}', github.event.repository.default_branch) }} - shell: bash - run: | - set -euo pipefail - gh workflow run ci-main.yml \ - -R "$GITHUB_REPOSITORY" \ - --ref "$DEFAULT_BRANCH" \ - -f scope="$DISPATCH_SCOPE" \ - -f base_sha="$DISPATCH_BASE_SHA" - nightly-red-to-signal: - name: "Control / Nightly red-to-signal" - if: ${{ github.event_name == 'workflow_dispatch' && inputs.simulate_failure }} - runs-on: ubuntu-24.04 - timeout-minutes: 5 - steps: - - name: Simulate nightly failure - shell: bash - run: | - set -euo pipefail - echo "nightly red-to-signal simulation requested" >&2 - exit 1 - nightly-alert: - name: "Control / Nightly red-to-signal" - if: ${{ always() && needs.nightly-red-to-signal.result == 'failure' }} - needs: [nightly-red-to-signal] - runs-on: ubuntu-24.04 - permissions: - contents: read - issues: write - steps: - - name: Open or update nightly failure signal - env: - GH_TOKEN: ${{ github.token }} - NIGHTLY_RESULT: ${{ needs.nightly-red-to-signal.result }} - shell: bash - run: | - set -euo pipefail - if [[ "$NIGHTLY_RESULT" == success ]]; then - exit 0 - fi - echo "::error::nightly-red-to-signal failed: $NIGHTLY_RESULT" - existing="$(gh api "repos/$GITHUB_REPOSITORY/issues?state=open" --jq '.[] | select(.title == "Nightly CI red") | .number' | sed -n '1p')" - body="$(printf '%s\n%s\n' \ - "nightly-red-to-signal result: $NIGHTLY_RESULT" \ - "Run: https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID")" - if [[ -n "$existing" ]]; then - gh api --method PATCH "repos/$GITHUB_REPOSITORY/issues/$existing" -f body="$body" >/dev/null - else - gh api --method POST "repos/$GITHUB_REPOSITORY/issues" -f title='Nightly CI red' -f body="$body" >/dev/null - fi diff --git a/.velnor/config.toml b/.velnor/config.toml new file mode 100644 index 0000000..6df794f --- /dev/null +++ b/.velnor/config.toml @@ -0,0 +1,48 @@ +# Generated by Velnor Actions 0.1.0; edit .velnor/config.toml and regenerate. +schema = 1 + +# Velnor replaces the entire .github tree on generate. Keep CODEOWNERS at the +# repository root or under docs/ (both are GitHub-recognized); anything inside +# .github is removed. + +# Optional workflow display and policy settings. Omitted values use Velnor defaults. +[workflow] +default_branch = "main" + + +# Optional resource limits for generated jobs. +# [resources] +# compiler_process_budget = 2 # MBX/Cargo compiler process budget. +# test_process_budget = 2 # Test process budget per generated lane. + +# Optional test partitioning. Keep one shard unless measurement justifies more. +# [test_sharding] +# default_shards = 1 # Default number of test partitions. +# by_manifest = {} # Manifest path to shard-count overrides. + +# Optional exact registered stack IDs to suppress after detection. +# [stacks] +# ignore = [] # Example: ["rust"]. + +# Optional Rust task configuration. The Rust detector is automatic in V1. +# [stacks.rust] +# configurations = [{ name = "default", features = ["default"], target = "host" }] +# compile_driver = "cargo" # Sticky override: "cargo" (default) or "mbx". Without it, a repo-local Mise Cargo wrapper selects MBX. Each key overrides its own axis only; conflicts with durable evidence fail closed. +# test_runner = "cargo_test" # Sticky override: "cargo_test" (default) or "cargo_nextest". Without it, .config/nextest.toml selects Nextest ([profile.ci] when declared, else the documented default profile). +# custom_tasks = [] # Allowlisted Mise task names (sorted, unique); only these run as `mise run ` steps in crate jobs. Each named task's Mise-defined commands execute in CI: review before allowlisting. + +# Optional repository-relative POSIX globs excluded before detector input. +# [discovery] +# exclude = [] + +# Optional exact action-pin overrides. Omitted names use Velnor's bundled latest pins. +# [actions.overrides] +# "jdx/mise-action" = { version = "v5.0.0", sha = "9149ea85001c7435d5a66bb127d6a1b6227cb0a5" } +# "actions/checkout" = { version = "v7.0.1", sha = "3d3c42e5aac5ba805825da76410c181273ba90b1" } +# "actions/cache/restore" = { version = "v6.1.0", sha = "55cc8345863c7cc4c66a329aec7e433d2d1c52a9" } +# "actions/cache/save" = { version = "v6.1.0", sha = "55cc8345863c7cc4c66a329aec7e433d2d1c52a9" } +# "actions/upload-artifact" = { version = "v7.0.1", sha = "043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" } +# "actions/download-artifact" = { version = "v8.0.1", sha = "3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c" } +# "jdx/mr-boxington-action" = { version = "v1.5.0", sha = "9df1d4b18b2147788a7ee7a2c7b84ecf62fd89d3" } +# "Swatinem/rust-cache" = { version = "v2.9.2", sha = "6323deb102c322ba6fcbdcafc7e3dddab59af2b6" } +# Values must be an allowlisted action's matching release version and full SHA. diff --git a/.velnor/release-manifest.json b/.velnor/release-manifest.json new file mode 100644 index 0000000..cd8c2a8 --- /dev/null +++ b/.velnor/release-manifest.json @@ -0,0 +1,18 @@ +{ + "schema": 1, + "version": "0.1.0", + "repository": "tailrocks/velnor-new", + "commit": "c57c700459bbe1549fe7eedcb7d8689585c38986", + "targets": [ + { + "target": "x86_64-unknown-linux-gnu", + "artifact": "https://github.com/tailrocks/velnor-new/releases/download/v0.1.0/velnor-actions-0.1.0-x86_64-unknown-linux-gnu", + "sha256": "aa7e44d6579e9c586106d120ed3658fcf1c9b041027ad9f03473e8efacd3b5d5" + }, + { + "target": "aarch64-apple-darwin", + "artifact": "https://github.com/tailrocks/velnor-new/releases/download/v0.1.0/velnor-actions-0.1.0-aarch64-apple-darwin", + "sha256": "b6f514b71e3d1d72978c66cecf23560e7f25ad51727e9f26c88870b77d61695f" + } + ] +}