Add a workflow for testing parser changes against a sandbox repo - #127
Conversation
Unit tests show that a feature parses its YAML and calls the API it means to call, but not that GitHub accepts the call or that the result is the one the contributor intended. Reviewers had no way to find that out short of pointing the CLI at a repository by hand, and authors had no way at all before asking for review. Add a workflow_dispatch workflow that applies a given version of the parser to a sandbox repository. It runs in two places. In the Apache repo a committer tests a pull request: a PR number resolves to refs/pull/<n>/head, a ref on the base repo, so PRs opened from forks can be tested without a mirror branch. In a contributor's own fork the author tests their own branch first. Nothing distinguishes the two beyond the repository the workflow runs in -- the sandbox defaults to the sandbox of whoever owns that repository, so a fork's run acts on the fork owner's sandbox with the fork owner's token, and cannot reach the Apache one. The run summary records the resolved ref, the commit SHA, the PR author and the .asf.yaml that was read, so a reviewer can confirm the run executed the commit they read. The .asf.yaml under test is read from a branch of the sandbox repo, which needs a branch the CLI could not express: asfyaml-run and asfyaml-validate hardcoded refs/heads/main, leaving branch-sensitive behaviour untestable. Both now take --branch. Dispatching executes code from the named ref with a token that has admin rights on a sandbox repo, so every input is passed through env rather than interpolated into run, a PR number is validated as digits before it reaches a ref, refs are held to characters a ref can legitimately contain, and a sandbox repo must be a well-formed owner/name. tests/sandbox_workflow.py executes that step's script under bash with hostile inputs; removing any of those guards fails those tests. docs/testing-a-change.md describes both paths end to end, including the four-step fork setup, resetting the sandbox afterwards, and the one-time setup infra needs to provide for the Apache repo: the sandbox repo, a sandbox environment, and a fine-grained token scoped to that repo alone. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018yUbPm1PL7tsKZJhXFsfj7
The repository had no contributor-facing instructions of any kind, so everything an agent or a newcomer needs -- that tests are not named test_*.py, that a feature has to be registered in feature/__init__.py, that a schema change is only real once README.md describes it -- had to be rediscovered by reading the configuration. Write it down, along with how to open a pull request and how to test a change against a sandbox repo in your own fork, pointing at docs/testing-a-change.md for the full process. Lead with the mistake that costs the most: asfyaml-run derives the GitHub repository it acts on from the name of the directory it is given, so a run pointed at a directory named after a real project changes that project's settings. CLAUDE.md and GEMINI.md are symlinks to AGENTS.md rather than copies, so the three cannot drift apart. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018yUbPm1PL7tsKZJhXFsfj7
There was a problem hiding this comment.
I was not sure if it was intentional to use older versions of the following actions but left a couple of potential changes.
The defined versions of actions/checkout and actions/setup-python has node20 runtime which is being deprecated/removed from the GitHub Action runners on September 23rd, 2026.
actions/checkoutupdated its runtime to node24 in version 5.actions/setup-pythonupdated its runtime to node24 in version 6.
https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
9162850 to
4fd9682
Compare
Co-authored-by: エリス <erisu@users.noreply.github.com>
Co-authored-by: エリス <erisu@users.noreply.github.com>
Co-authored-by: エリス <erisu@users.noreply.github.com>
Dependabot PR is coming :) |
|
:D ? |
|
The dependabot is there #129 And I hope we can get that one quickly to unblock testing for .asfyaml. |
|
I think a number of people would love to be able to move faster with .asfyaml - and testing seems to be bottleneck here - we have many PRs open now. @Humbedooh -> is there anything blocking us here? |
Why
Our unit tests show that a feature parses its YAML and calls the API it means to
call. They don't show that GitHub accepts the call, or that the result is the one
the contributor had in mind. Today a reviewer who wants to know that has to point
the CLI at some repository by hand, an author has no way to check at all before
asking for review, and there's no shared account of how any of it is done.
What this adds
A
workflow_dispatchworkflow (Test a change against the sandbox repo) thatapplies a given version of the parser to a sandbox repo. Inputs:
pr(orref),sandbox_branch,sandbox_repo, andnoop(defaulting to a dry run).It runs in two places, with no fork-specific code path:
refs/pull/<n>/head— a ref on the base repo — so PRs opened from forks can betested without pushing a mirror branch anywhere.
anyone to look. The sandbox defaults to the sandbox repo of whoever owns the
repository the workflow is running in, so a fork's run acts on the fork owner's
sandbox with the fork owner's token — and cannot reach the Apache sandbox
through it. The contributor needs four things once (Actions enabled on the
fork, their own sandbox repo, a
sandboxenvironment, a token scoped to thatrepo); the docs walk through it.
The run summary records the resolved ref, the commit SHA, the PR author, and the
.asf.yamlthat was read, so a reviewer can confirm the run executed the committhey actually read.
--branchonasfyaml-runandasfyaml-validate. Both hardcodedrefs/heads/main, so nothing branch-sensitive (whoami, website staging andpublishing,
ghp_branch) could be exercised, and the workflow needs to name thesandbox branch carrying the config under test.
mainandrefs/heads/mainareboth accepted; tag refs are rejected.
docs/testing-a-change.md— both paths end to end: read the diff → push atest/pr-NNNbranch to the sandbox → dry run → apply → verify (with aper-directive table of where to look in Settings) → reset the sandbox → report
the result on the PR. Plus the fork setup, the local-CLI equivalent, and the things
that catch people out: why the workflow dropdown stays on
main, whygithub:metadata applies regardless of which branch it was read from while
publish:doesn't, and that
noopstill reads from the API.AGENTS.md, withCLAUDE.mdandGEMINI.mdas symlinks to it. The repo had nocontributor-facing instructions at all, so things that are only discoverable by
reading configuration — that tests are not named
test_*.py(python_files = "*.py"), that a feature is invisible unless it's imported inasfyaml/feature/__init__.py, that a schema change isn't real untilREADME.mddescribes it — had to be rediscovered each time. It leads with the mistake that
costs most:
asfyaml-runderives the repository it acts on from the directoryname it is given, so a run pointed at a directory called
airflowreconfiguresapache/airflow. Then setup, conventions, how to test in your own fork (pointinghere for the full process), and how to open a PR. Symlinks rather than copies so
the three files cannot drift apart.
Security model — worth a careful look
Dispatching in this repo executes code from the named ref with a token that has
admin rights on the Apache sandbox, and for a fork PR that's code from outside
the project.
workflow_dispatchlimits triggering to people with write access, andthat person is the control: read the diff, then check the SHA in the run summary
matches what you read. (A run in one's own fork spends nobody else's trust, which
is the other argument for self-testing first.)
Hardening in the workflow itself: every input goes through
env:rather than beinginterpolated into
run:; a PR number is validated as^[0-9]+$before it reaches aref; refs are held to characters a ref can legitimately contain; a sandbox repo must
be a well-formed
owner/name; the workflow definition always comes frommain, soa PR can't rewrite the runner.
tests/sandbox_workflow.pyextracts that step's script from the YAML and runs itunder bash with hostile inputs (
42; whoami,$(whoami),owner/repo; whoami,embedded newlines that would inject a second
$GITHUB_OUTPUTline). Each guard wasdeleted in turn to confirm the tests fail without it.
If committer-only dispatch is judged too weak, adding required reviewers to the
sandboxenvironment tightens it with no change to this workflow.Needs infra before it can run here
The workflow fails with an explicit error rather than silently no-opping if these
are missing. Contributors testing in their own forks need none of it — they set up
their own equivalents.
apache/infrastructure-asfyaml-sandbox, public, with a baseline.asf.yamlonmain— the reset step re-applies it.sandboxenvironment on this repo.ASFYAML_SANDBOX_TOKENon that environment: a fine-grained PAT whose repositoryaccess is only the sandbox repo. A
GITHUB_TOKENcan't change repositorysettings, so a PAT is unavoidable here.
Testing
38 new tests, all passing (136 total). The CLI tests were written first and watched
to fail. The workflow tests were written against an existing YAML, so each guard was
removed in turn to confirm the tests catch its absence.
AGENTS.mdadds no tests; its factual claims were checked againstpyproject.toml,.pre-commit-config.yamlandasfyaml/feature/__init__.pyrather than asserted.🤖 Generated with Claude Code
https://claude.ai/code/session_018yUbPm1PL7tsKZJhXFsfj7