One vendor/bin/proctor check grades a PHP app against CREED's version policy: the PHP floor, patch levels, Composer constraints, the lock file, composer audit, MySQL 5.6 safety, portability and both CI pipelines. It writes text, JSON, GitLab Code Quality or SARIF, and exits 0, 1 or 2. The invigilator your composer.lock didn't know it needed: no phones, no EOL PHP.
Read these before you turn the page. The proctor will not repeat them.
Bring your own lock.
PROCTOR reads the app's composer.json and composer.lock, and package-lock.json for Vue and Inertia. It never runs your app.
The questions are set by CREED.
Support dates, the grace window and the audit threshold come from the policy file passed with --policy. PROCTOR holds no policy of its own, and a PHP version missing from the policy is a failure, not a pass.
Every answer is written in words.
Each line starts PASS, INFO, WARN or FAIL and the last line says RESULT. No colour, no ANSI codes: meaning never rides on colour alone.
Three ways to leave the room.
0 pass, warnings allowed. 1 one or more failures. 2 a tool error: bad options, an unreadable policy, a malformed composer.json. Exit 2 means the proctor tripped, not you.
Notes from home expire.
An override needs a reason, an expiry and an approver, and is printed on every run. Past end of life plus grace, no note excuses you.
Part A
The questions
Nine checks, asked in this order on every run. Each one shows a real line from PROCTOR's text report, taken from its test fixtures.
Q1php-floor
Is the lowest PHP your composer.json allows still supported?
Active passes. Security-only warns. Past end of life, or no usable php constraint, fails. The floor comes from the constraint, not from the PHP that runs the check.
FAIL php-floor: php floor 7.4 is eol_grace_expired
Q2unpatched
Is every locked component on its line's latest patch?
Reads Laravel from composer.lock, Vue and Inertia from package-lock.json, and PHP from --php-runtime. Behind is a warning; the owner picks the day.
WARN unpatched: laravel 12.1.3 is behind 12.4.7 (checked 2026-10-28); update to the latest patch.
Q3unbounded-constraints
Is every runtime requirement tied to a released range?
A * or a dev- branch fails, including dev-master as 1.0. require-dev, php, ext-* and lib-* are skipped.
FAIL unbounded-constraints: vendor/any constraint "*" is unbounded
Q4lock-committed
Did you hand in your composer.lock?
Missing from the app path fails. In CI the working tree is a clone, so present means committed.
PASS lock-committed: composer.lock is present
Q5portability
Would the app still run on another host?
Warns on an absolute host path, a hard-coded proxy address, sessions on local disk, or a php_value line in .htaccess. Citadel, OpenShift, Coolify or plain Docker: same app.
WARN portability: host-path: absolute host path "/var/log/app.log"; read it from env or config
Q6mysql56
Will your migrations survive the shared MySQL 5.6 server?
Reads the code as a syntax tree, not as lines, so a pattern in a comment or a string is not flagged: JSON and generated columns, renameColumn, the upsert alias, long indexed strings. Warnings only; the proctor raises an eyebrow.
WARN mysql56: mysql56-json: JSON columns and JSON queries need MySQL 5.7.8
Q7ci-pipelines
Do both pipelines run composer ci?
For waynestate/ packages a missing GitLab or GitHub pipeline fails; for everyone else a missing GitLab one is a note. A GitHub uses: ref that is not a commit SHA warns.
WARN ci-pipelines: ci-pipelines-gitlab-missing: .gitlab-ci.yml is missing; add a GitLab pipeline with a job whose script runs composer ci
Q8composer-audit
Does the lock carry a known advisory?
Fails at or above the policy's audit_severity_threshold, warns below it. An advisory with no severity counts as failing: unknown input never turns into a pass.
PASS composer-audit: no advisories found
Q9overrides
Has any note from home expired?
Every expired entry in policy-overrides.json fails. Active ones are printed on every run, so nothing is waived quietly.
FAIL overrides: override for "lock-committed" expired on 2026-01-01
Part B: next term Coming soon
Set, not yet on the paper. No commands until they are real.
The release gate.proctor gate against the image that will actually serve the app, with a ratchet that blocks new problems before old ones.
Security headers and CSP. HELMET sets them; PROCTOR will check they are there.
The WayneUI contract. Is the interface the shared one, at a supported version?
Node versions. Today only the Vue and Inertia locks are read.
Lock in sync. A lock whose hash no longer matches composer.json.
The policy's own banned list. Read rules.banned_constraints instead of a list in code.
Marking
The answer sheet marks itself
One run of proctor check on a small app, from pencils up to the stamp. Scroll, and keep your eyes on your own paper.
1 · Pencils up
The candidate is your-app. The policy is CREED's, today's date is fixed, and the proctor is watching.
vendor/bin/proctor check --policy=policy.json
2 · Questions 1 and 2
PHP 8.4 is in active support: pass. Laravel is a few patches behind: a warning, not a failure.
PASS php-floor: php floor 8.4 is active
WARN unpatched: laravel 12.1.3 is behind 12.4.7 (checked 2026-10-28); update to the latest patch.
3 · Question 3: red pen
One requirement says "*". That fails, and the margin says what to write instead.
FAIL unbounded-constraints: vendor/any constraint "*" is unbounded
4 · Questions 4 to 9
Lock present, nothing hard-coded, no migrations to worry about, both pipelines, a clean audit, no expired notes. One failure is still one failure.
RESULT: fail (1 failures, 1 warnings)
5 · Resit, pencils down
Pin "vendor/any": "^2.0", run it again. Question 3 is erased and passes; the patch warning stays on the sheet, and warnings are allowed. Exit 0.
PASS unbounded-constraints: all require constraints are bounded
RESULT: pass
PROCTOR answer sheet Form 0.1
Nameyour-appDate2026-10-28
Fill one bubble per question. P pass · I info · W warn · F fail
The marked answer sheet: final answer for each check after the resit. Question 3 failed first and passed on the resit.
Question
Check
Answer
1
php-floor
pass
2
unpatched
warn
3
unbounded-constraints
fail, then pass on the resit
4
lock-committed
pass
5
portability
pass
6
mysql56
pass
7
ci-pipelines
pass
8
composer-audit
pass
9
overrides
pass
Red pen, Q3: "*" is unbounded.pin it: "vendor/any": "^2.0"
Pick the copy your reader needs with --format. The findings are the same in all four; these are real runs on PROCTOR's unbounded-app fixture, trimmed where marked.
White copy · for people · --format=text (the default)
PASS php-floor: php floor 8.4 is active
FAIL unbounded-constraints: vendor/dev constraint "dev-master" is unbounded
FAIL unbounded-constraints: vendor/any constraint "*" is unbounded
PASS lock-committed: composer.lock is present
PASS portability: no hard-coded platform facts found
PASS mysql56: no Laravel migrations found; skipped
PASS ci-pipelines: both pipelines run composer ci
WARN composer-audit: composer audit could not run: No installed packages found. …
RESULT: fail (2 failures, 1 warnings)
The mark sheet, filed in the format the head office already reads.
0
Pass. Warnings allowed.
1
One or more failures.
2
Tool error. The proctor tripped over the chair.
Absences
A note from home
An app can carry policy-overrides.json next to its composer.json. An active override turns a check's failures into warnings. An expired one fails the overrides check, and every override is printed on every run.
Excused absenceNo. 001
[
{
"check": "lock-committed",
"reason": "lock is generated by CI and not committed",
"expires": "2027-01-31",
"approved_by": "jane@example.wayne.edu"
}
]
signed, a real approver
When the note runs out
FAIL overrides: override for "lock-committed" expired on 2026-01-01
OVERRIDE lock-committed: fixture: expired (expires 2026-01-01, approved by tester)
RESULT: fail (1 failures, 1 warnings)
No note excuses end of life plus grace. A failure in the eol_grace_expired state stands, whatever the app carries.
Exam halls
Sitting it in CI
The same command on both forges. PREFLIGHT books the GitLab hall; on the GitHub mirror the SARIF copy goes to code scanning.
Options: --path=. · --format=text|json|codequality|sarif · --policy=<file> (required) · --today=YYYY-MM-DD for runs that agree on any day · --php-runtime=X.Y.Z for the unpatched check.
Seating chart
Who else is in the room
PROCTOR is one part of WELD, Wayne State's app stack. The proctor sits at the front; everyone else sits the same exam.