Skip to content

[HOLD-for-9.0.0][skip-runtime-e2e] docs(decisions): canonical /decisions verdict values #80

[HOLD-for-9.0.0][skip-runtime-e2e] docs(decisions): canonical /decisions verdict values

[HOLD-for-9.0.0][skip-runtime-e2e] docs(decisions): canonical /decisions verdict values #80

name: Definition of Done
# Enforces CLAUDE.md HARD RULE #0: a user-facing feature is not done until
# demonstrated through its actual runtime. See axonflow-claude-plugin#59
# for the doctrine + lint-no-mocks rationale.
on:
pull_request:
# Drop `edited` — re-runs the gate on title/body edits without any code change.
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
lint-no-mocks-in-runtime-e2e:
name: Lint — no mocks in runtime-e2e/
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run lint
run: |
if [ -x scripts/lint-no-mocks-in-runtime-e2e.sh ]; then
./scripts/lint-no-mocks-in-runtime-e2e.sh
else
echo "lint-no-mocks-in-runtime-e2e.sh not present — skipping (older branch)."
fi
runtime-e2e-required:
name: Runtime E2E required for user-facing changes
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect user-facing surface changes
id: detect
env:
BASE: ${{ github.event.pull_request.base.sha }}
HEAD: ${{ github.event.pull_request.head.sha }}
run: |
set -uo pipefail
# Python SDK user-facing surface: the published package source
# (axonflow/), build/install metadata (pyproject.toml), and the
# examples/ tree that downstream users copy from. Changes here
# are observable from a `pip install axonflow` + `from axonflow
# import AxonFlow` flow against a real running agent.
USER_FACING_GLOBS=(
'axonflow/'
'pyproject.toml'
'examples/'
)
CHANGED=$(git diff --name-only "$BASE" "$HEAD" || true)
echo "Changed files in PR:" >&2
printf ' %s\n' $CHANGED >&2
MATCHED=""
for f in $CHANGED; do
for pat in "${USER_FACING_GLOBS[@]}"; do
case "$f" in
"$pat"*|*"/$pat"*)
MATCHED="$MATCHED $f"
break
;;
esac
done
done
RUNTIME_E2E_TOUCHED=$(echo "$CHANGED" | grep -c '^runtime-e2e/' || true)
{
echo "user_facing_changed=$([ -n "$MATCHED" ] && echo true || echo false)"
echo "runtime_e2e_touched=$RUNTIME_E2E_TOUCHED"
} >> "$GITHUB_OUTPUT"
if [ -n "$MATCHED" ]; then
echo "User-facing files changed:" >&2
for f in $MATCHED; do echo " - $f" >&2; done
fi
- name: Check escape-hatch justification
id: hatch
if: steps.detect.outputs.user_facing_changed == 'true' && steps.detect.outputs.runtime_e2e_touched == '0'
env:
PR_TITLE: ${{ github.event.pull_request.title }}
PR_BODY: ${{ github.event.pull_request.body }}
run: |
set -uo pipefail
if [[ "$PR_TITLE" == *"[skip-runtime-e2e]"* ]]; then
if echo "$PR_BODY" | grep -q '## Skip-runtime-e2e justification'; then
echo "Escape hatch active." >&2
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "::error::PR title carries [skip-runtime-e2e] but body has no '## Skip-runtime-e2e justification' section."
exit 1
fi
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
- name: Enforce runtime-e2e/ presence
if: steps.detect.outputs.user_facing_changed == 'true' && steps.detect.outputs.runtime_e2e_touched == '0' && steps.hatch.outputs.skip != 'true'
run: |
cat <<'EOF' >&2
::error::This PR touches real SDK code (axonflow/, pyproject.toml, examples/)
without runtime-e2e/ test in the same PR.
Per CLAUDE.md HARD RULE #0:
A user-facing feature is not done until you have demonstrated it
working through its actual runtime — a real `from axonflow import
AxonFlow` over real httpx against a real running AxonFlow agent.
Mocks, stubs, MagicMock, httpx_mock.add_response, and capture-stub
harnesses do NOT count as runtime proof.
To resolve, do ONE of:
1. Add a test under runtime-e2e/<feature>/test.py that invokes
the SDK against a real running agent and asserts on the
agent's response (not on a mocked response object).
2. If genuinely internal (build / deps / lint baseline / docs),
add `[skip-runtime-e2e]` to PR title AND a
`## Skip-runtime-e2e justification` section to PR body.
See: axonflow-internal-docs/engineering/E2E_EXAMPLES_TESTING_WORKFLOW.md
EOF
exit 1
- name: All clear
if: steps.detect.outputs.user_facing_changed == 'false' || steps.detect.outputs.runtime_e2e_touched != '0' || steps.hatch.outputs.skip == 'true'
run: |
if [ "${{ steps.detect.outputs.user_facing_changed }}" = 'false' ]; then
echo "No user-facing surface changed. Gate not applicable." >&2
elif [ "${{ steps.detect.outputs.runtime_e2e_touched }}" != '0' ]; then
echo "User-facing change detected and runtime-e2e/ updated in same PR. ✓" >&2
else
echo "Escape hatch active with valid justification. ✓" >&2
fi