From 12ee712d1bf94f2e97957845caa542e1c27582a5 Mon Sep 17 00:00:00 2001 From: Postil Maintainer Date: Sun, 19 Jul 2026 23:59:39 +0000 Subject: [PATCH 1/3] Pin Postil CLI v0.7.2 --- .github/workflows/ci.yml | 75 ++++++++++++++++++++++++++++++++++++---- README.md | 6 ++-- action.yml | 7 +++- 3 files changed, 78 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2854cd3..803e5ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,6 +59,15 @@ jobs: if "run" in step and "shell" not in step: errors.append(f"steps[{i}] has 'run' but no 'shell'") + review_steps = [step for step in (steps or []) if step.get("name") == "Review"] + if len(review_steps) != 1: + errors.append("runs.steps must contain exactly one Review step") + else: + review_script = review_steps[0].get("run", "") + for argument in ("--publish", "--bounded", "--sha", "--base-sha", "--output-json"): + if argument not in review_script: + errors.append(f"Review step must pass {argument}") + for name, spec in (doc.get("inputs") or {}).items(): if spec.get("required") and "default" in spec: errors.append(f"input '{name}' is required but also sets a default") @@ -276,6 +285,7 @@ jobs: envelope_path=$(awk -F= '$1 == "envelope-path" { print substr($0, index($0, "=") + 1) }' "$output") cmp "$fixture" "$envelope_path" jq -e '.reviewCoverage == {mode:"bounded",selectedBatches:5,totalBatches:17,plannerFallback:true,nested:{retained:[1,2,3]}}' "$envelope_path" + grep -Fx -- '--publish' "$args_log" grep -Fx -- '--bounded' "$args_log" config_temp="$RUNNER_TEMP/review-config" @@ -294,6 +304,7 @@ jobs: INPUT_SOFT_FAIL=false INPUT_SARIF_PATH='' EVENT_PR='' \ FIXTURE_ENVELOPE="$fixture" ARGS_LOG="$config_args" \ bash "${review_script[0]}" + grep -Fx -- '--publish' "$config_args" grep -Fx -- '--bounded' "$config_args" grep -Fx -- '--config' "$config_args" grep -Fx -- 'postil.toml' "$config_args" @@ -367,6 +378,49 @@ jobs: [ "$status" -eq 2 ] grep -F "postil review did not complete (exit 2)" "$no_model_log" + - name: Bind event reviews to the triggering snapshot + run: | + set -euo pipefail + review_script=("$RUNNER_TEMP"/action-scripts/*-Review.sh) + [ "${#review_script[@]}" -eq 1 ] + fake_bin="$RUNNER_TEMP/fake-review-bin" + fixture="$RUNNER_TEMP/envelope-fixture.json" + + run_review() { + case_name="$1" + input_pr="$2" + args_log="$RUNNER_TEMP/$case_name-args.txt" + case_output="$RUNNER_TEMP/$case_name-outputs.txt" + case_temp="$RUNNER_TEMP/$case_name" + mkdir -p "$case_temp" + PATH="$fake_bin:$PATH" \ + RUNNER_TEMP="$case_temp" GITHUB_OUTPUT="$case_output" \ + GITHUB_REPOSITORY=postil-dev/postil-action GITHUB_TOKEN=test-token \ + POSTIL_API_KEY=test-key POSTIL_API_BASE=https://provider.example/v1 \ + POSTIL_API_FORMAT=anthropic POSTIL_ENDPOINT_AUTH_HEADER=X-Gateway-Token \ + POSTIL_ENDPOINT_AUTH_VALUE=private-endpoint-secret \ + POSTIL_ALLOW_PRIVATE_API_BASE=true REVIEW_MODEL=qualified/model \ + REVIEW_MODEL_CASCADE='' INPUT_PR="$input_pr" INPUT_FAIL_ON='' INPUT_CONFIG='' \ + INPUT_SINCE_SHA='' INPUT_BASELINE='' INPUT_SOFT_FAIL=false \ + INPUT_SARIF_PATH='' EVENT_PR=7 EVENT_HEAD_SHA=head-from-event \ + EVENT_BASE_SHA=base-from-event FIXTURE_ENVELOPE="$fixture" ARGS_LOG="$args_log" \ + bash "${review_script[0]}" + } + + run_review event-snapshot '' + snapshot_args="$RUNNER_TEMP/event-snapshot-args.txt" + grep -Fx -- '--sha' "$snapshot_args" + grep -Fx -- 'head-from-event' "$snapshot_args" + grep -Fx -- '--base-sha' "$snapshot_args" + grep -Fx -- 'base-from-event' "$snapshot_args" + + run_review explicit-pr 8 + explicit_args="$RUNNER_TEMP/explicit-pr-args.txt" + if grep -Fx -- '--sha' "$explicit_args" || grep -Fx -- '--base-sha' "$explicit_args"; then + echo "::error::explicit non-event PR inherited the event snapshot" + exit 1 + fi + - name: Fall back safely for missing or tampered release bundles run: | set -euo pipefail @@ -494,7 +548,7 @@ jobs: : >"$case_dir/cosign.log" set +e PATH="$fake_bin:$PATH" RUNNER_TEMP="$case_dir/temp" RUNNER_OS=Linux \ - GITHUB_PATH="$case_dir/github-path" CLI_REF="$CLI_REF" CLI_RELEASE=v0.6.2 \ + GITHUB_PATH="$case_dir/github-path" CLI_REF="$CLI_REF" CLI_RELEASE=v0.7.2 \ GH_TOKEN=test-token POSTIL_TARGET=x86_64-unknown-linux-gnu \ RELEASE_TEST_MODE="$mode" FAKE_GIT_LOG="$case_dir/git.log" \ FAKE_CARGO_LOG="$case_dir/cargo.log" FAKE_COSIGN_LOG="$case_dir/cosign.log" \ @@ -576,7 +630,7 @@ jobs: - name: Signed postil-cli source fallback verifies with production policy env: - CLI_REF: 3c5a1b8a0f24a0d71d342b44c6ce64c638830da6 # postil-cli v0.6.2 + CLI_REF: dcf4e34c4804d4bf64705ab6367c883cea23b33a # postil-cli v0.7.2 run: | set -euo pipefail export GNUPGHOME="$RUNNER_TEMP/postil-gnupg" @@ -654,24 +708,31 @@ jobs: include: - target: x86_64-unknown-linux-gnu runner: ubuntu-latest + sha256: e3aa51d0147c40281aadf533610f45b7ede3070911d52be723e70b04360935aa - target: x86_64-unknown-linux-musl runner: ubuntu-latest + sha256: 5e710bf3cec119cfc0d9a6381075e993155eca8eecfa0573e8301a7b194b85fe - target: aarch64-unknown-linux-gnu runner: ubuntu-24.04-arm + sha256: 1387159dffc5237e141b62bd16ff30b8a125a2f5ca6f1318d66d6fcb25ee20a4 - target: aarch64-unknown-linux-musl runner: ubuntu-24.04-arm + sha256: 80c23018f8988b7cf9bb16a3c0e3ae86d64bc90fcd2bc28ce3c39694948568fb - target: x86_64-apple-darwin runner: macos-15-intel + sha256: 4d4e05498eb370b8c83668bad927216c264e2383977e7c1d045281add4a79a3b - target: aarch64-apple-darwin runner: macos-15 + sha256: 7ceb39a59d60c63fb4df1f6918180d0486fba81f728206a89a28693b13d670b9 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3 - name: Fetch, cosign-verify, and smoke test the pinned CLI release env: - CLI_REF: 3c5a1b8a0f24a0d71d342b44c6ce64c638830da6 # postil-cli v0.6.2 - CLI_RELEASE: v0.6.2 - EXPECTED_VERSION: postil 0.6.2 + CLI_REF: dcf4e34c4804d4bf64705ab6367c883cea23b33a # postil-cli v0.7.2 + CLI_RELEASE: v0.7.2 + EXPECTED_VERSION: postil 0.7.2 + EXPECTED_SHA256: ${{ matrix.sha256 }} TARGET: ${{ matrix.target }} GH_TOKEN: ${{ github.token }} run: | @@ -688,6 +749,8 @@ jobs: curl -fsSL --retry 3 --retry-connrefused --retry-delay 2 -o "$DEST/postil.tar.gz.sig" "$base/postil-$TARGET.tar.gz.sig" curl -fsSL --retry 3 --retry-connrefused --retry-delay 2 -o "$DEST/postil.tar.gz.pem" "$base/postil-$TARGET.tar.gz.pem" + checksum_line=$(cat "$DEST/postil.tar.gz.sha256") + [ "$checksum_line" = "$EXPECTED_SHA256 postil-$TARGET.tar.gz" ] expected=$(awk '{print $1}' "$DEST/postil.tar.gz.sha256") if command -v sha256sum >/dev/null 2>&1; then actual=$(sha256sum "$DEST/postil.tar.gz" | awk '{print $1}') @@ -744,5 +807,5 @@ jobs: # Boundary: this is as far as the smoke test goes. The action's # "Review" step, deliberately not exercised here, takes it from - # here to `postil review --repo ... --pr ...`, which needs a real + # here to `postil review --publish --bounded --repo ... --pr ...`, which needs a real # PR and a working model API key. diff --git a/README.md b/README.md index 7cb0378..6978841 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ jobs: steps: - uses: postil-dev/postil-action@00442e2340edaa4a681955dbb25e20650ca1514c with: - cli-ref: fbbe0891bc302a7644c6fb2232ee5f4af587894b - cli-release: v0.7.1 + cli-ref: dcf4e34c4804d4bf64705ab6367c883cea23b33a + cli-release: v0.7.2 api-key: ${{ secrets.OPENROUTER_API_KEY }} model: ${{ vars.POSTIL_REVIEW_MODEL }} ``` @@ -56,7 +56,7 @@ Set the `POSTIL_REVIEW_MODEL` repository variable to a model qualified for your Outputs are `envelope-path`, `gate-failing`, and `sarif-path`. -The action does not select an unverified fallback model. Set `model` to a model qualified for your review profile, or provide trusted configuration with `config`. If resolution produces no model, Postil fails before contacting a provider. `envelope-path` points to the complete CLI envelope, including `reviewCoverage` when the review uses bounded source selection. +The action does not select an unverified fallback model. Set `model` to a model qualified for your review profile, or provide trusted configuration with `config`. If resolution produces no model, Postil fails before contacting a provider. Reviews cover up to five selected source batches. `envelope-path` preserves the complete CLI envelope, including `reviewCoverage`. For the native Anthropic Messages API, set `api-format: anthropic` and its API base. Private gateways can use `endpoint-auth-header` and `endpoint-auth-value` for one additional credential; set both or neither. Set `allow-private-api-base: true` only for an endpoint whose network boundary you trust. diff --git a/action.yml b/action.yml index 666d113..af25c44 100644 --- a/action.yml +++ b/action.yml @@ -327,6 +327,8 @@ runs: INPUT_SOFT_FAIL: ${{ inputs.soft-fail }} INPUT_SARIF_PATH: ${{ inputs.sarif-path }} EVENT_PR: ${{ github.event.pull_request.number }} + EVENT_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + EVENT_BASE_SHA: ${{ github.event.pull_request.base.sha }} run: | set -euo pipefail # A set-but-empty key must not shadow a job-level OPENROUTER_API_KEY. @@ -337,7 +339,10 @@ runs: exit 1 fi ENVELOPE="$RUNNER_TEMP/postil-envelope.json" - args=(review --bounded --repo "$GITHUB_REPOSITORY" --pr "$PR" --output-json) + args=(review --publish --bounded --repo "$GITHUB_REPOSITORY" --pr "$PR" --output-json) + if [ "$PR" = "${EVENT_PR:-}" ] && [ -n "${EVENT_HEAD_SHA:-}" ] && [ -n "${EVENT_BASE_SHA:-}" ]; then + args+=(--sha "$EVENT_HEAD_SHA" --base-sha "$EVENT_BASE_SHA") + fi [ -n "$INPUT_FAIL_ON" ] && args+=(--fail-on "$INPUT_FAIL_ON") [ -n "$INPUT_CONFIG" ] && args+=(--config "$INPUT_CONFIG") [ -n "$INPUT_SINCE_SHA" ] && args+=(--since-sha "$INPUT_SINCE_SHA") From 998aff26b493b88b4f4cb53e3418cae794070a49 Mon Sep 17 00:00:00 2001 From: Postil Maintainer Date: Mon, 20 Jul 2026 00:01:33 +0000 Subject: [PATCH 2/3] Clarify action credential fixtures --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 803e5ee..bc0bc07 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -255,7 +255,7 @@ jobs: fi [ "${POSTIL_API_FORMAT:-}" = "anthropic" ] [ "${POSTIL_ENDPOINT_AUTH_HEADER:-}" = "X-Gateway-Token" ] - [ "${POSTIL_ENDPOINT_AUTH_VALUE:-}" = "private-endpoint-secret" ] + [ "${POSTIL_ENDPOINT_AUTH_VALUE:-}" = "fixture-gateway-value" ] [ "${POSTIL_ALLOW_PRIVATE_API_BASE:-}" = "true" ] cat "$FIXTURE_ENVELOPE" exit "${FIXTURE_EXIT:-0}" @@ -276,7 +276,7 @@ jobs: GITHUB_REPOSITORY=postil-dev/postil-action GITHUB_TOKEN=test-token \ POSTIL_API_KEY=test-key POSTIL_API_BASE=https://provider.example/v1 \ POSTIL_API_FORMAT=anthropic POSTIL_ENDPOINT_AUTH_HEADER=X-Gateway-Token \ - POSTIL_ENDPOINT_AUTH_VALUE=private-endpoint-secret \ + POSTIL_ENDPOINT_AUTH_VALUE=fixture-gateway-value \ POSTIL_ALLOW_PRIVATE_API_BASE=true REVIEW_MODEL=qualified/model \ REVIEW_MODEL_CASCADE='' INPUT_PR=7 INPUT_FAIL_ON='' INPUT_CONFIG='' \ INPUT_SINCE_SHA='' INPUT_BASELINE='' INPUT_SOFT_FAIL=false \ @@ -297,7 +297,7 @@ jobs: GITHUB_REPOSITORY=postil-dev/postil-action GITHUB_TOKEN=test-token \ POSTIL_API_KEY=test-key POSTIL_API_BASE=https://provider.example/v1 \ POSTIL_API_FORMAT=anthropic POSTIL_ENDPOINT_AUTH_HEADER=X-Gateway-Token \ - POSTIL_ENDPOINT_AUTH_VALUE=private-endpoint-secret \ + POSTIL_ENDPOINT_AUTH_VALUE=fixture-gateway-value \ POSTIL_ALLOW_PRIVATE_API_BASE=true REVIEW_MODEL='' \ REVIEW_MODEL_CASCADE='' INPUT_PR=7 INPUT_FAIL_ON='' \ INPUT_CONFIG=postil.toml INPUT_SINCE_SHA='' INPUT_BASELINE='' \ @@ -325,7 +325,7 @@ jobs: GITHUB_REPOSITORY=postil-dev/postil-action GITHUB_TOKEN=test-token \ POSTIL_API_KEY=test-key POSTIL_API_BASE=https://provider.example/v1 \ POSTIL_API_FORMAT=anthropic POSTIL_ENDPOINT_AUTH_HEADER=X-Gateway-Token \ - POSTIL_ENDPOINT_AUTH_VALUE=private-endpoint-secret \ + POSTIL_ENDPOINT_AUTH_VALUE=fixture-gateway-value \ POSTIL_ALLOW_PRIVATE_API_BASE=true REVIEW_MODEL=qualified/model \ REVIEW_MODEL_CASCADE='' INPUT_PR=7 INPUT_FAIL_ON='' INPUT_CONFIG='' \ INPUT_SINCE_SHA='' INPUT_BASELINE='' INPUT_SOFT_FAIL=false \ @@ -398,7 +398,7 @@ jobs: GITHUB_REPOSITORY=postil-dev/postil-action GITHUB_TOKEN=test-token \ POSTIL_API_KEY=test-key POSTIL_API_BASE=https://provider.example/v1 \ POSTIL_API_FORMAT=anthropic POSTIL_ENDPOINT_AUTH_HEADER=X-Gateway-Token \ - POSTIL_ENDPOINT_AUTH_VALUE=private-endpoint-secret \ + POSTIL_ENDPOINT_AUTH_VALUE=fixture-gateway-value \ POSTIL_ALLOW_PRIVATE_API_BASE=true REVIEW_MODEL=qualified/model \ REVIEW_MODEL_CASCADE='' INPUT_PR="$input_pr" INPUT_FAIL_ON='' INPUT_CONFIG='' \ INPUT_SINCE_SHA='' INPUT_BASELINE='' INPUT_SOFT_FAIL=false \ From 441cbd5335b65bdeb091bc4a53fb2b89dce1c97d Mon Sep 17 00:00:00 2001 From: Postil Maintainer Date: Mon, 20 Jul 2026 00:02:20 +0000 Subject: [PATCH 3/3] Keep snapshot checks behavioral --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc0bc07..3cffde8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,7 +64,7 @@ jobs: errors.append("runs.steps must contain exactly one Review step") else: review_script = review_steps[0].get("run", "") - for argument in ("--publish", "--bounded", "--sha", "--base-sha", "--output-json"): + for argument in ("--publish", "--bounded", "--output-json"): if argument not in review_script: errors.append(f"Review step must pass {argument}")