Skip to content

refactor(pat): split PatValidator::validate into probe, status and scope steps - #556

Merged
rubenvdlinde merged 2 commits into
developmentfrom
refactor/sig-pat-validate
Nov 2, 2026
Merged

rubenvdlinde merged 2 commits into
developmentfrom
refactor/sig-pat-validate

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

What

PatValidator::validate was one 70-line unit with a cyclomatic complexity of 16. Its public signature is unchanged. It now runs the same steps in the same order:

  1. the unknown-forge rejection (fail closed, in-band), the forge and the token kind;
  2. probe: the user-endpoint request with probeHeaders (Authorization from the forge's scheme, the GitHub Accept and API-version headers), http_errors off and the local-address setting; an unreachable forge is logged and rejected;
  3. statusRejection: 401, 403 and any other non-200 status;
  4. the unverifiable-scope acceptance for forges that do not expose token scopes;
  5. scopeResult and classicScopeResult: the scope and expiry headers, the least-privilege check for a classic token, the warning for a fine-grained one.

Each new step takes at most two parameters. The token is only ever passed to Forge::authHeaderValue, as before.

Review requested (credentials): this is the token validation path (least-privilege scope enforcement, where the token is sent). It is behaviour-preserving by the golden test below, but please read it as a REVIEW PR.

SIG numbers (gate 122, five-star profile, php)

check development this branch 4-star 5-star
size >15 64.13% 63.41% 47.1% 23.6%
size >30 36.15% 35.23% 23.1% 11.6%
size >60 13.95% 13.08% 8.3% 4.2%
CCN >5 53.65% 52.68% 20.2% 10.1%
CCN >10 29.78% 28.87% 7.3% 3.7%
params >=3 31.42% 31.34% 15.0% 7.5%

validate is now 23 lines, complexity 5. No check flips on its own.

Verified

  • Golden test tests/unit/Service/Pat/PatValidatorGoldenTest.php runs 147 scenarios: five forges (GitHub, Codeberg, a configured Forgejo host, an unknown and an empty id) x three token kinds x six probe outcomes, an unconfigured Forgejo, the local-address setting, and per token kind 13 scope-header shapes (missing, empty, blank, allowed, extra, several extra, string-valued, lowercase key, empty list, non-string values) and 5 expiry-header shapes. Per case it compares, as JSON text, the probe request (URL and every option, including the Authorization header), the config read, every logged warning and the whole ValidationResult. Recorded on the old code in the first commit, re-run plain on old and new: identical.
  • Mutants (prlimit --as=1.5G timeout 120, signal-safe runner, file restored after each round): new code 17 of 18 killed, old code 6 of 7 killed, the no-op control survives in both. The survivor in both is equivalent: === KIND_CLASSIC vs !== KIND_FINE_GRAINED, as detectKind only returns those two.
  • Unit suite (tests/phpunit-unit-only.xml): 758 tests OK. Under pcov: 758 OK, 0 risky. Psalm (level 1, full lib): no errors. php-cs-fixer: 0 to fix. Gate 122 ratchet: 0 findings. Gate 16: count 0. Gate 119: 0.

AI-assisted (Claude Code, claude-opus-5-5).

…ope steps

The forge probe, the status answers, and the scope and expiry evaluation
are separate steps, each taking at most two parameters. Behaviour, the
probe request and every result are unchanged (pinned by the golden
scenario set).

Assisted-by: ClaudeCode:claude-opus-5-5
@rubenvdlinde
rubenvdlinde merged commit eec67ef into development Nov 2, 2026
47 of 49 checks passed
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/versioniq @ c27f9a2

Check PHP Vue Security License Tests
lint ✅
phpcs ✅
phpmd ✅
psalm ✅
phpstan ✅
phpmetrics ✅
eslint ✅
stylelint ✅
build ✅
composer ✅ ✅ 32/32
npm ❌ ✅ 302/302
app:check-code ⏭️
info.xml ✅
REUSE ❌
lockfile sync ✅
PHPUnit ✅ 759/759 (8 legs)
Newman ⏭️
Playwright ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development.
Hydra gates ✅

Quality workflow — 2026-11-02 09:49 UTC

Download the full PDF report from the workflow artifacts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant