Skip to content
PROCTOR Form 0.1

The stack's check command

PROCTOR: Watches the exam. Doesn't help.

PROCTOR stands for Policy Rules Of Code, Tested On Release.

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.

composer require waynestate/proctor --dev

Sit the exam Watch it mark a paper

  • 9 questions
  • 4 report formats
  • 3 exit codes
  • 0 policy of its own

Page 1

Instructions to candidates

Read these before you turn the page. The proctor will not repeat them.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  1. 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

  2. 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.

  3. 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

  4. 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

  5. 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

  6. 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

  7. 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

  8. 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

  9. 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. 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. 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. 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. 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. 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-app Date2026-10-28

The marked answer sheet: final answer for each check after the resit. Question 3 failed first and passed on the resit.
QuestionCheckAnswer
1php-floorpass
2unpatchedwarn
3unbounded-constraintsfail, then pass on the resit
4lock-committedpass
5portabilitypass
6mysql56pass
7ci-pipelinespass
8composer-auditpass
9overridespass

Red pen, Q3: "*" is unbounded. pin it: "vendor/any": "^2.0"

Result First run: fail (1 failures, 1 warnings) Resit: pass

PASSexit 0 · warnings allowed

Hand in

Four copies of the same paper

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)
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.

Hall G GitLab, through PREFLIGHT

include:
  - component: $CI_SERVER_FQDN/enterprise-apps/preflight/proctor@0.1.0
    inputs:
      proctor_command: vendor/bin/proctor check --format=codequality --policy=vendor/waynestate/creed/policy/policy.json

The job publishes a Code Quality report, so findings show on the merge request. Strongly recommended: a git.wayne.edu instance runner.

Hall H GitHub code scanning

- run: composer require waynestate/proctor --dev
# policy.json is the CREED policy file
- run: vendor/bin/proctor check --format=sarif --policy=policy.json > proctor.sarif
- uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
  with:
    sarif_file: proctor.sarif

Pinned to a full commit SHA, the way ci-pipelines asks. Needs permissions: security-events: write.

In CI, pass --php-runtime the deploy image's PHP, not the runner's: the app is served by the image.

Begin

You may turn over your paper

Install PROCTOR and CREED's policy as dev dependencies, then run the check from the app's folder.

  1. Install, in a terminal:

    composer require waynestate/proctor waynestate/creed --dev
  2. Sit the exam:

    vendor/bin/proctor check --policy=vendor/waynestate/creed/policy/policy.json

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.

PROCTOR mark: a watchful eye with a gold square pupil

PROCTOR

Watches the exam. Doesn't help.

The class

  • WayneUI mark: a rounded frame holding a solid form, with a gold tab

    WayneUI

    The interface every page is drawn with.

  • CREED mark

    CREED

    Sets the questions: the policy.json PROCTOR reads.

  • PREFLIGHT mark

    PREFLIGHT

    Books the hall: shared CI that runs PROCTOR.

  • Pinstripe mark

    Pinstripe

    Neat handwriting: the coding standard.

  • SESAME mark

    SESAME

    Checks ID at the door: sign-in.

  • HELMET mark

    HELMET

    Security headers and runtime hardening.

  • Sourdough mark

    Sourdough

    Starters that arrive ready to sit the exam.

  • WELD mark

    WELD

    The whole stack, joined; runs proctor check in CI.

Transfer students, new this term

  • BOOSTER mark

    BOOSTER

    Upgrades you to a passing PHP, one hop at a time.

  • ROLLCALL mark

    ROLLCALL

    Every app answers “present”, with its PHP version.

  • SQUIRE mark

    SQUIRE

    Carries the passed paper to Citadel.

  • DRAWBRIDGE mark

    DRAWBRIDGE

    The Citadel sign-in gateway.

  • ADVISOR mark

    ADVISOR

    Ask what applies to your app, and what to do next.