Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/no-mistakes-gate-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: no-mistakes-gate-test

# The attestation gate is an inline shell script inside
# .github/workflows/no-mistakes-required.yml, so nothing else exercises it.
# test/no-mistakes-gate.test.mjs extracts that exact block and runs it against
# fixtures; run it whenever the gate or its test changes.
on:
push:
branches: [main]
paths:
- .github/workflows/no-mistakes-required.yml
- .github/workflows/no-mistakes-gate-test.yml
- test/no-mistakes-gate.test.mjs
- package.json
- pnpm-lock.yaml
pull_request:
branches: [main]
paths:
- .github/workflows/no-mistakes-required.yml
- .github/workflows/no-mistakes-gate-test.yml
- test/no-mistakes-gate.test.mjs
- package.json
- pnpm-lock.yaml

permissions:
contents: read

jobs:
gate:
name: no-mistakes gate behaves as specified
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm

- run: pnpm install --frozen-lockfile
- run: pnpm run test:workflows
192 changes: 176 additions & 16 deletions .github/workflows/no-mistakes-required.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
github.event.pull_request.user.login != 'dependabot[bot]' &&
github.event.pull_request.user.login != 'release-please[bot]'
steps:
- name: Verify no-mistakes signature in PR body
- name: Verify no-mistakes signature and pipeline attestation in PR body
env:
PR_BODY: ${{ github.event.pull_request.body }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
Expand All @@ -49,19 +49,179 @@ jobs:
marker='Updates from [git push no-mistakes](https://github.com/kunchenguid/no-mistakes)'
if printf '%s' "${PR_BODY:-}" | grep -qF -- "$marker"; then
echo "Found no-mistakes signature in PR #${PR_NUMBER} body."
exit 0
else
{
echo "::error::This PR was not raised through no-mistakes."
echo
echo "Contributions to this repository must be submitted via 'git push no-mistakes'."
echo "That pipeline runs the required review/test/lint/CI steps and writes a"
echo "deterministic '## Pipeline' section into the PR body containing:"
echo
echo " $marker"
echo
echo "See CONTRIBUTING.md for setup and the full workflow."
echo
echo "PR author: ${PR_AUTHOR}"
} >&2
exit 1
fi
{
echo "::error::This PR was not raised through no-mistakes."
echo
echo "Contributions to this repository must be submitted via 'git push no-mistakes'."
echo "That pipeline runs the required review/test/build/CI steps and writes a"
echo "deterministic '## Pipeline' section into the PR body containing:"
echo
echo " $marker"
echo
echo "See CONTRIBUTING.md for setup and the full workflow."
echo
echo "PR author: ${PR_AUTHOR}"
} >&2
exit 1

# The signature alone only proves the pipeline wrote the body. no-mistakes
# >= 1.46.0 also emits a machine-readable step attestation next to it; parse
# that to prove review, test, and document actually ran to completion.
# Contract: docs/reference/pipeline-steps.md#pipeline-step-attestation in
# kunchenguid/no-mistakes.
attestation_prefix='<!-- no-mistakes-pipeline-attestation:v1 '
attestation_suffix=' -->'

if ! printf '%s' "${PR_BODY:-}" | grep -qF -- "$attestation_prefix"; then
echo "::error::This PR carries the no-mistakes signature but no pipeline attestation."
{
echo
echo "no-mistakes >= 1.46.0 is required (PR 670). That release writes a"
echo "machine-readable comment next to the signature:"
echo
echo " ${attestation_prefix}{\"head_sha\":\"...\",\"steps\":[...]}${attestation_suffix}"
echo
echo "Upgrade no-mistakes ('no-mistakes update'), then re-run"
echo "'git push no-mistakes' so the PR body is rewritten with the attestation."
echo
echo "PR author: ${PR_AUTHOR}"
} >&2
exit 1
fi

# Exact-substring extraction (no regex): first prefix, then the first
# closing token after it, mirroring how no-mistakes' own consumer test
# slices the payload.
payload="$(
printf '%s' "${PR_BODY:-}" | tr -d '\r' | awk -v pre="$attestation_prefix" -v suf="$attestation_suffix" '
found { next }
{
p = index($0, pre)
if (p == 0) next
rest = substr($0, p + length(pre))
s = index(rest, suf)
if (s == 0) next
print substr(rest, 1, s - 1)
found = 1
}
'
)"

if [ -z "$payload" ]; then
echo "::error::The no-mistakes pipeline attestation comment is malformed: no JSON payload could be extracted."
{
echo
echo "The '${attestation_prefix}' marker is present but is not closed by"
echo "'${attestation_suffix}' on the same line, or the payload is empty."
echo "This gate fails closed on an unreadable attestation."
echo
echo "PR author: ${PR_AUTHOR}"
} >&2
exit 1
fi

# Real JSON parsing. Emits one "<step>\t<verdict>\t<detail>" line per
# required step. A step recorded more than once must be completed in
# every record. Any skip-shaped sibling key (a future 'skipped',
# 'skip_reason', 'quota_...', '..._unavailable' field) with a meaningful
# value is rejected outright, so a skip can never ride along on a
# 'completed' status.
if ! report="$(
printf '%s' "$payload" | jq -r --argjson required '["review","test","document"]' '
if type != "object" then error("attestation payload is not a JSON object") else . end
| if (.steps | type) != "array" then error("attestation payload has no \"steps\" array") else . end
| . as $attestation
| $required[]
| . as $name
| [ $attestation.steps[] | select((type == "object") and ((.step? | tostring) == $name)) ] as $records
| if ($records | length) == 0 then
"\($name)\tmissing\tno record in attestation"
else
([ $records[] | (.status? // null) | tostring ] | unique) as $statuses
| ([ $records[]
| to_entries[]
| select((.key | ascii_downcase) | test("skip|quota|unavailable"))
| select(.value != null and .value != false and .value != "")
| .key ] | unique) as $skip_markers
| if ($skip_markers | length) > 0 then
"\($name)\tskip-marker\t\($skip_markers | join(","))"
elif $statuses == ["completed"] then
"\($name)\tok\tcompleted"
else
"\($name)\tbad\t\($statuses | join(","))"
end
end
' 2>&1
)"; then
echo "::error::The no-mistakes pipeline attestation payload could not be parsed as JSON."
{
echo
echo "jq reported:"
echo "$report"
echo
echo "Payload: $payload"
echo
echo "This gate fails closed on an unparseable attestation."
echo
echo "PR author: ${PR_AUTHOR}"
} >&2
exit 1
fi

echo "Attestation head_sha: $(printf '%s' "$payload" | jq -r '.head_sha // "(absent)"')"

tab="$(printf '\t')"
gate_status=0
checked=0
while IFS="$tab" read -r name verdict detail; do
[ -n "$name" ] || continue
checked=$((checked + 1))
case "$verdict" in
ok)
echo " ok: ${name} = completed"
;;
missing)
echo "::error::The no-mistakes pipeline attestation has no '${name}' step record; '${name}' must be recorded as completed."
gate_status=1
;;
skip-marker)
echo "::error::The no-mistakes pipeline attestation marks '${name}' with skip indicator(s) [${detail}]; quota-exhaustion and agent-unavailability skips are not accepted."
gate_status=1
;;
*)
echo "::error::The no-mistakes pipeline attestation records '${name}' as '${detail}', not 'completed'."
gate_status=1
;;
esac
done <<REPORT
$report
REPORT

# A verdict per required step is the only shape jq can emit for a payload
# it accepted. Assert it anyway so an unexpected empty report fails closed
# instead of reporting nothing and passing.
if [ "$checked" -ne 3 ]; then
echo "::error::The no-mistakes attestation gate reached a verdict for ${checked} of the 3 required steps (review, test, document); failing closed."
gate_status=1
fi

if [ "$gate_status" -ne 0 ]; then
{
echo
echo "The no-mistakes review, test, and document steps must all be recorded as"
echo "'completed'. A step that was skipped (pre-skipped with --skip, skipped at a"
echo "gate, or skipped because the agent was unavailable or out of quota), failed,"
echo "or never finished is not accepted."
echo
echo "Re-run 'git push no-mistakes' and let review, test, and document run."
echo
echo "Attestation payload: $payload"
echo
echo "PR author: ${PR_AUTHOR}"
} >&2
exit 1
fi

echo "no-mistakes pipeline attestation verified for PR #${PR_NUMBER}: review, test, and document all completed."
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ Because the release PR carries the version bump, a downstream `*-axi` tool only
- The AXI catalog and principle summaries are single-sourced from `catalog.yaml` and `principles.yaml`.
`pnpm run docs:gen` rewrites the marked `generated:...` regions of README.md and docs/index.html; never hand-edit those regions.
The `docs-check` workflow runs `pnpm run docs:check` and fails on drift, including when `.agents/skills/axi/SKILL.md` section headings stop matching the canonical principle titles.
- The no-mistakes PR gate in `.github/workflows/no-mistakes-required.yml` is one self-contained inline `run:` script mirrored across sibling repos from `kunchenguid/gh-axi`; port that script byte-for-byte and keep this repo's own `on:`/`paths-ignore`/`if:` exemptions.
`pnpm run test:workflows` extracts that exact block and executes it against fixtures (`test/no-mistakes-gate.test.mjs`, node:test), and the `no-mistakes-gate-test` workflow runs it in CI.

## Maintaining this file

Expand Down
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default defineConfig(
files: [
"packages/axi-sdk-js/**/*.{js,cjs,mjs,ts,cts,mts}",
"scripts/**/*.mjs",
"test/**/*.mjs",
],
languageOptions: {
globals: {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"docs:gen": "node scripts/generate-docs.mjs",
"docs:check": "node scripts/generate-docs.mjs --check",
"docs:test": "node --test scripts/generate-docs.test.mjs",
"lint": "eslint packages/axi-sdk-js/src packages/axi-sdk-js/test eslint.config.mjs scripts",
"test:workflows": "node --test test/no-mistakes-gate.test.mjs",
"lint": "eslint packages/axi-sdk-js/src packages/axi-sdk-js/test eslint.config.mjs scripts test",
"format": "prettier --write \"packages/axi-sdk-js/**/*.{ts,js,mjs,cjs,json,md}\" \".github/workflows/axi-sdk-js-*.yml\" \"release-please-config.json\" \".release-please-manifest.json\"",
"format:check": "prettier --check \"packages/axi-sdk-js/**/*.{ts,js,mjs,cjs,json,md}\" \".github/workflows/axi-sdk-js-*.yml\" \"release-please-config.json\" \".release-please-manifest.json\""
},
Expand Down
1 change: 1 addition & 0 deletions packages/axi-sdk-js/test/release-ci-exclusions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ describe("release-please CI exclusions", () => {
"axi-sdk-js-ci.yml",
"docs-check.yml",
"guard-generated-files.yml",
"no-mistakes-gate-test.yml",
"no-mistakes-required.yml",
]);

Expand Down
Loading
Loading