- permissions: contents: read — this workflow only builds and tests,
it never writes to the repo; matches the convention deploy.yml
already sets.
- timeout-minutes: 15 on the job so a hung step doesn't burn runner
time indefinitely.
- concurrency group keyed on the ref so a new push cancels the
in-flight run for the same branch/PR instead of queuing behind it.
Action versions are left unpinned (still @v4) — SHA-pinning only this
workflow while deploy.yml stays on @v4 would be an inconsistency to
clean up in its own pass, not here.