@@ -281,198 +281,102 @@ runs:
281281 node-version : ' 20'
282282
283283 # ---------------------------------------------------------------------------
284- # 7. Run the PINNED published CLI in PR-scan mode over the diff.
285- # IMPORTANT: the CLI version is pinned by THIS Action release (no @latest at
286- # runtime — ADR-0035 §2, review #4). Update CLI_VERSION when cutting a new
287- # Action release. Pinned to the published 1.5.0 (npmjs `latest`), which ships PR-scan mode.
288- # We run the pinned package directly with npx --yes so no project install /
289- # postinstall of the PR head executes.
284+ # 7. Run the pinned, published CLI in PR-scan mode.
285+ # In --pr-scan the CLI does the whole flow itself: resolves the base..head
286+ # diff LOCALLY (git), sends only the changed files' contents to linter-server
287+ # (which normalizes + HMAC-signs the findings as a scanProof), mints the
288+ # Actions OIDC token from ACTIONS_ID_TOKEN_REQUEST_* (needs id-token: write),
289+ # and submits to the ci-review-service (POST <ci-service-url>/v1/findings),
290+ # which verifies origin + content and posts AS THE APP. No project build /
291+ # postinstall of PR-head code ever runs.
292+ #
293+ # CLI_VERSION is pinned by THIS Action release (never @latest at runtime).
290294 # ---------------------------------------------------------------------------
291295 - id : scan
292296 if : steps.preflight.outputs.ready == 'true'
293297 shell : bash
294298 env :
295- # SA creds — passed to the CLI, which authenticates over WSS to linter-server.
299+ # SA creds — the CLI authenticates over WSS to linter-server with these .
296300 BROWSERSTACK_USERNAME : ${{ inputs.username }}
297301 BROWSERSTACK_ACCESS_KEY : ${{ inputs.access-key }}
298- CHANGED_FILES : ${{ steps.pr.outputs.changed-files }}
302+ # scanProof context: {repo, prNumber, headSha}. repo/pr-number are required
303+ # by --pr-scan; base is the diff baseline (CI checkouts can't infer it);
304+ # head-sha binds the proof to this PR head (defaults to git HEAD otherwise).
305+ REPO : ${{ github.repository }}
306+ PR_NUMBER : ${{ steps.pr.outputs.pr-number }}
299307 BASE_SHA : ${{ steps.pr.outputs.base-sha }}
300308 HEAD_SHA : ${{ steps.pr.outputs.head-sha }}
309+ FAIL_ON : ${{ inputs.fail-on-severity }}
310+ # ACTIONS_ID_TOKEN_REQUEST_URL/TOKEN are inherited from the job env
311+ # (permissions: id-token: write); the CLI reads them to mint the OIDC token.
301312 run : |
302313 set -euo pipefail
303- # Pinned published CLI version (npmjs latest = 1.5.0; includes PR- scan mode ). Do NOT use @latest.
314+ # Pinned published CLI (npmjs latest = 1.5.0; ships --pr- scan). Do NOT use @latest.
304315 CLI_VERSION="1.5.0"
305- FINDINGS_OUT="${RUNNER_TEMP}/findings.json"
306316
307- echo "Running @browserstack/accessibility-devtools@${CLI_VERSION} in PR-scan mode (diff scope)…"
308- # --scope=diff (ADR-0022): scan only changed ranges over base…head.
309- # The CLI reads file contents; it does NOT execute the project.
310- npx --yes "@browserstack/accessibility-devtools@${CLI_VERSION}" scan \
311- --scope=diff \
317+ # fail-on-severity=none -> --non-strict: report findings but never fail the
318+ # job. `error` is the CLI default (errors -> exit 1). NOTE: CLI 1.5.0 has no
319+ # separate "warning" gate, so `warning` currently behaves like `error`.
320+ NON_STRICT=()
321+ if [ "${FAIL_ON}" = "none" ]; then NON_STRICT=(--non-strict); fi
322+
323+ echo "Running @browserstack/accessibility-devtools@${CLI_VERSION} --pr-scan (diff scope)…"
324+ set +e
325+ npx --yes "@browserstack/accessibility-devtools@${CLI_VERSION}" \
326+ --pr-scan \
327+ --include '**/*' \
312328 --base "${BASE_SHA}" \
313- --head "${HEAD_SHA}" \
314- --changed-files "${CHANGED_FILES}" \
315- --output "${FINDINGS_OUT}" \
316- --format json
317-
318- echo "findings-file=${FINDINGS_OUT}" >> "$GITHUB_OUTPUT"
329+ --head-sha "${HEAD_SHA}" \
330+ --repo "${REPO}" \
331+ --pr-number "${PR_NUMBER}" \
332+ --ci-service-url "https://devtools-a11y-linter.browserstack.com" \
333+ "${NON_STRICT[@]}"
334+ code=$?
335+ set -e
336+ echo "scan-exit=${code}" >> "$GITHUB_OUTPUT"
337+ echo "CLI exited ${code}"
319338
320339 # ---------------------------------------------------------------------------
321- # 8. Request the Actions OIDC token with the DevA11y audience (ADR-0035 §5).
322- # Proves "this request came from a real Actions run in repo X" — repo
323- # granularity, no user resolved. Audience matches the deployed
324- # ci-review-service verifier config.
325- # ---------------------------------------------------------------------------
326- - id : oidc
327- if : steps.preflight.outputs.ready == 'true'
328- shell : bash
329- run : |
330- set -euo pipefail
331- # DevA11y OIDC audience — matches the ci-review-service verifier config (oidc.audience).
332- AUDIENCE="browserstack-accessibility-devtools"
333- token="$(curl -sf \
334- -H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
335- "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=${AUDIENCE}" | jq -r '.value')"
336- if [ -z "$token" ] || [ "$token" = "null" ]; then
337- echo "::error::Failed to mint the Actions OIDC token."
338- exit 1
339- fi
340- # Mask so it never lands in logs.
341- echo "::add-mask::$token"
342- echo "token=$token" >> "$GITHUB_OUTPUT"
343-
344- # ---------------------------------------------------------------------------
345- # 9. Submit findings + scanProof + OIDC token to the ci-review-service.
346- # The service verifies origin (OIDC) + content (scanProof) and posts AS THE
347- # APP. The Action never posts to GitHub directly (ADR-0035 §3, §5).
348- # The service base URL is fixed (global ingress), NOT a customer input.
349- # ---------------------------------------------------------------------------
350- - id : submit
351- if : steps.preflight.outputs.ready == 'true'
352- shell : bash
353- env :
354- OIDC_TOKEN : ${{ steps.oidc.outputs.token }}
355- FINDINGS_FILE : ${{ steps.scan.outputs.findings-file }}
356- REPO : ${{ github.repository }}
357- PR_NUMBER : ${{ steps.pr.outputs.pr-number }}
358- HEAD_SHA : ${{ steps.pr.outputs.head-sha }}
359- OPT_COMMENT : ${{ inputs.comment }}
360- OPT_CHECK_GATE : ${{ inputs.check-gate }}
361- OPT_FAIL_ON : ${{ inputs.fail-on-severity }}
362- OPT_INLINE : ${{ inputs.inline-suggestions }}
363- OPT_SARIF : ${{ inputs.sarif }}
364- OPT_COMMENT_MODE : ${{ inputs.comment-mode }}
365- OPT_REMEDIATION : ${{ inputs.remediation }}
366- OPT_AI_AGENT : ${{ inputs.ai-agent }}
367- run : |
368- set -euo pipefail
369- # Service base URL (global ingress); server-side geo-routes per data-residency
370- # region (ADR-0035 §7). NOT a customer input.
371- SERVICE_URL="https://devtools-a11y-linter.browserstack.com"
372- RESP_OUT="${RUNNER_TEMP}/service-response.json"
373-
374- # The CLI emits { findings, scanProof } — findings are normalized and the
375- # scanProof is HMAC-signed by linter-server (key never on the runner).
376- findings="$(jq -c '.findings // .' "${FINDINGS_FILE}")"
377- scan_proof="$(jq -r '.scanProof // ""' "${FINDINGS_FILE}")"
378-
379- # Assemble the request body (ADR-0035 §5 wire shape).
380- body="$(jq -cn \
381- --arg repo "$REPO" \
382- --argjson prNumber "$PR_NUMBER" \
383- --arg headSha "$HEAD_SHA" \
384- --argjson findings "$findings" \
385- --arg scanProof "$scan_proof" \
386- --arg oidcToken "$OIDC_TOKEN" \
387- --arg comment "$OPT_COMMENT" \
388- --arg checkGate "$OPT_CHECK_GATE" \
389- --arg failOnSeverity "$OPT_FAIL_ON" \
390- --arg inlineSuggestions "$OPT_INLINE" \
391- --arg sarif "$OPT_SARIF" \
392- --arg commentMode "$OPT_COMMENT_MODE" \
393- --arg remediation "$OPT_REMEDIATION" \
394- --arg aiAgent "$OPT_AI_AGENT" \
395- '{
396- platform: "github",
397- repo: $repo,
398- prNumber: $prNumber,
399- headSha: $headSha,
400- findings: $findings,
401- scanProof: $scanProof,
402- oidcToken: $oidcToken,
403- options: {
404- comment: ($comment == "true"),
405- checkGate: ($checkGate == "true"),
406- failOnSeverity: $failOnSeverity,
407- inlineSuggestions: ($inlineSuggestions == "true"),
408- sarif: ($sarif == "true"),
409- commentMode: $commentMode,
410- remediation: ($remediation == "true"),
411- aiAgent: $aiAgent
412- }
413- }')"
414-
415- http_code="$(curl -sS -o "${RESP_OUT}" -w '%{http_code}' \
416- -X POST "${SERVICE_URL}/v1/findings" \
417- -H "Content-Type: application/json" \
418- -H "X-CI-Platform: github" \
419- --data-binary "${body}")"
420-
421- echo "ci-review-service responded HTTP ${http_code}"
422- if [ "$http_code" -lt 200 ] || [ "$http_code" -ge 300 ]; then
423- echo "::error::ci-review-service rejected the submission (HTTP ${http_code})."
424- cat "${RESP_OUT}" || true
425- exit 1
426- fi
427- echo "response-file=${RESP_OUT}" >> "$GITHUB_OUTPUT"
428-
429- # ---------------------------------------------------------------------------
430- # 10. Report: surface outputs from the service response and apply the gate.
340+ # 8. Report + gate. The CLI already submitted the findings and the App posts
341+ # them; here we only translate the CLI exit code into the job result and the
342+ # optional merge gate. (CLI 1.5.0 --pr-scan does not return per-severity
343+ # counts / comment URL to the runner, nor forward the comment/inline/sarif/
344+ # remediation options — the ci-review-service applies its defaults. Surfacing
345+ # those needs a CLI enhancement.)
431346 # ---------------------------------------------------------------------------
432347 - id : report
433348 if : always()
434349 shell : bash
435350 env :
436- RESP_FILE : ${{ steps.submit .outputs.response-file }}
351+ SCAN_EXIT : ${{ steps.scan .outputs.scan-exit }}
437352 PROCEED : ${{ steps.guard.outputs.proceed }}
438353 ALLOWED : ${{ steps.permission.outputs.allowed }}
439354 READY : ${{ steps.preflight.outputs.ready }}
440355 OPT_CHECK_GATE : ${{ inputs.check-gate }}
441356 run : |
442357 set -euo pipefail
443-
444- # Default (skipped / not-run) outputs.
445- result="pass"; error_count="0"; warning_count="0"; findings_count="0"
446- comment_url=""; sarif_file=""
447-
358+ result="pass"
448359 if [ "${PROCEED}" != "true" ] || [ "${ALLOWED:-false}" != "true" ] || [ "${READY:-false}" != "true" ]; then
449360 echo "::notice::Scan did not run (guard/permission/preflight skip); reporting a neutral pass."
450- elif [ -n "${RESP_FILE:-}" ] && [ -f "${RESP_FILE}" ]; then
451- result="$(jq -r '.result // "pass"' "${RESP_FILE}")"
452- error_count="$(jq -r '.errorCount // 0' "${RESP_FILE}")"
453- warning_count="$(jq -r '.warningCount // 0' "${RESP_FILE}")"
454- findings_count="$(jq -r '.findingsCount // 0' "${RESP_FILE}")"
455- comment_url="$(jq -r '.commentUrl // ""' "${RESP_FILE}")"
456- sarif_file="$(jq -r '.sarifFile // ""' "${RESP_FILE}")"
457361 else
458- echo "::warning::No service response available; reporting fail."
459- result="fail"
362+ case "${SCAN_EXIT:-2}" in
363+ 0) result="pass" ;;
364+ 1) result="fail"; echo "::warning::Accessibility errors found." ;;
365+ 3) result="fail"; echo "::error::Fair-Usage wait budget exhausted (rate limited)." ;;
366+ 4) result="fail"; echo "::error::Permission denied by the server for this account/commenter." ;;
367+ *) result="fail"; echo "::error::Scan failed (CLI exit ${SCAN_EXIT:-unknown})." ;;
368+ esac
460369 fi
461-
462370 {
463- echo "result=$result"
464- echo "error-count=$error_count "
465- echo "warning-count=$warning_count "
466- echo "findings-count=$findings_count "
467- echo "comment-url=$comment_url "
468- echo "sarif-file=$sarif_file "
371+ echo "result=${ result} "
372+ echo "error-count="
373+ echo "warning-count="
374+ echo "findings-count="
375+ echo "comment-url="
376+ echo "sarif-file="
469377 } >> "$GITHUB_OUTPUT"
470-
471- echo "Result: ${result} (errors=${error_count}, warnings=${warning_count}, total=${findings_count})"
472- [ -n "$comment_url" ] && echo "Comment: ${comment_url}" || true
473-
474- # Gate: fail the job when check-gate is on and the service verdict is fail.
475- if [ "${OPT_CHECK_GATE}" = "true" ] && [ "$result" = "fail" ]; then
476- echo "::error::Accessibility check failed (fail-on-severity gate)."
378+ echo "Result: ${result} (CLI exit ${SCAN_EXIT:-n/a})"
379+ if [ "${OPT_CHECK_GATE}" = "true" ] && [ "${result}" = "fail" ]; then
380+ echo "::error::Accessibility check failed (check-gate)."
477381 exit 1
478382 fi
0 commit comments