Fleet attendance, Wayne State University
ROLLCALL
Every app answers “present”.
The ones on PHP 7.4 answer from the back row.
WELD's fleet scanner. For every app it writes down the PHP image, the declared PHP floor, Laravel, the auth package and the PROCTOR result, then checks each one against CREED's policy.json by date. One snapshot a day gives you drift and an EOL burn-down.
composer require waynestate/rollcall
- kiln-bookingPHP 8.5present
- tuba-rentalPHP 8.4present
- pigeon-postPHP 8.3late
- lunch-ledgerPHP 8.3late
- abacus-classicPHP 7.4back row
Answered 5 of 5
The magic trick is attendance
Take the roll
Seventeen apps, seated front to back by PHP version. Scroll, and ROLLCALL calls each name down the list. Everyone answers. The back row answers with a hand in the air.
Sample data · fictional apps · policy as of 2026-10-28
| No. | App | PHP image | Laravel | Auth | PROCTOR | Mark |
|---|---|---|---|---|---|---|
| Front row PHP 8.5 and 8.4, active | ||||||
| 01 | kiln-booking | 8.5 | 13 | sesame | pass | present |
| 02 | tuba-rental | 8.4 | 13 | sesame | pass | present |
| 03 | moth-catalog | 8.4 | 12 | sesame | pass | present |
| 04 | quad-weather | 8.4 | 13 | none | pass | present |
| 05 | greenhouse-log | 8.4 | 12 | sesame | pass | present |
| 06 | sundial | 8.4 | 13 | sesame | pass | present |
| 07 | otter-tracker | 8.4 | 12 | sesame | pass | present |
| Middle rows PHP 8.3 and 8.2, security fixes only | ||||||
| 08 | pigeon-post | 8.3 | 12 | phpcas | pass | late |
| 09 | lunch-ledger | 8.3 | 12 | sesame | fail | late |
| 10 | chess-club | 8.3 | 11, EOL | phpcas | pass | late |
| 11 | lost-mittens | 8.3 | 12 | none | none | late |
| 12 | fog-index | 8.2 64 days left | 11, EOL | phpcas | pass | late |
| Back row past end of life | ||||||
| 13 | abacus-classic | 7.4 | 8, EOL | phpcas | none | back row |
| 14 | punch-card-pal | 7.4 | none | none | none | back row |
| 15 | dial-up-directory | 8.1 | 10, EOL | phpcas | fail | back row |
| 16 | floppy-archive | 8.0 | 9, EOL | sesame | pass | back row |
| 17 | ghost-kiosk | legacy-base no version in the tag | none | none | none | inaudible |
- present
- 7
- late
- 5
- back row
- 4
- inaudible
- 1
- Answered
- 17
Nobody is ever marked absent: a gap in one source becomes a note on that app, never a failed scan. The public report shows only totals like these; the per-app list is the internal view (--internal) and stays inside the project.
Five answers per app
What “present” means
Each answer is a version, and every version is checked against the dates in CREED's policy.json. State is never decided in code.
PHP image
The runtime the app really ships on, read from the app list's image tag.
PHP floor
The lowest PHP its
composer.jsonallows.*counts as no floor at all.Laravel
The installed version from
composer.lock, never a guess.Auth package
Which sign-in package is installed: SESAME, a CAS client, or none.
PROCTOR
pass,failornone, from PROCTOR's JSON report. ROLLCALL never re-runs a rule.
Key to the marks
- present
active: fully supported - late
security_only: fixes only, a date is coming - back row
eol: past its security date, or older than anything the policy lists - inaudible
unknown: newer than the policy knows, or a tag nobody can read
Drift and the burn-down
Watch the back row empty
One snapshot per scan day. Count the apps on an EOL image each day and you get a line that should only go one way.
Grafana draws the same line from /metrics with the bundled dashboard, grafana/rollcall-burn-down.json.
Drift, counted per kind
image_eolruns on an EOL PHP imageimage_unknownan image tag nobody can readimage_below_floorthe image is older than the app allowsfloor_allows_eolthe PHP floor still lets an EOL version infloor_unboundedno PHP floor declared at alleol_past_gracepast the policy's grace dayslaravel_eolLaravel past its security dateproctor_missingno PROCTOR report on fileproctor_failingPROCTOR says failoverride_expireda PROCTOR override ran out
rollcall drift also compares the policy with a saved endoflife.date page. A difference is a flag for a person; the policy stays the source of truth.
From a checkout, with the bundled fixtures
Quick start
No network, no token. PHP 8.3 or newer. Install dependencies once with composer install, then call the roll.
bash
export ROLLCALL_POLICY_FILE=tests/fixtures/policy.json export ROLLCALL_REPOS_DIR=tests/fixtures/repos export ROLLCALL_IMAGES_FILE=tests/fixtures/images.json export ROLLCALL_PROCTOR_DIR=tests/fixtures/proctor export ROLLCALL_STORE_DIR=var/snapshots php bin/rollcall scan php bin/rollcall report
PowerShell, no bash or WSL needed
$env:ROLLCALL_POLICY_FILE = "tests/fixtures/policy.json" $env:ROLLCALL_REPOS_DIR = "tests/fixtures/repos" $env:ROLLCALL_IMAGES_FILE = "tests/fixtures/images.json" $env:ROLLCALL_PROCTOR_DIR = "tests/fixtures/proctor" $env:ROLLCALL_STORE_DIR = "var/snapshots" php bin/rollcall scan php bin/rollcall report
What comes back real output, ROLLCALL 0.1, bundled fixtures (four demo apps)
Present: 4 apps. Snapshot 2026-10-28 saved to var/snapshots. ROLLCALL 0.1.0 | scan 2026-10-28 | as of 2026-10-28 | public Apps present: 4 (scan problems: 0) PHP image VERSION APPS STATE SECURITY UNTIL DAYS TO EOL 8.1 1 eol 2025-12-31 -301 8.3 1 security_only 2027-12-31 429 8.4 1 active 2028-12-31 795 unknown 1 unknown - - EOL burn-down (state on each scan day) DATE APPS EOL SUPPORTED UNKNOWN 2026-10-28 4 1 2 1
exit 0, however the fleet looks. There is no exit 1: ROLLCALL takes attendance, it does not hand out detention.
rollcall scan- Calls the roll once and stores
<date>.json. Scan twice in a day and that day's snapshot is replaced. rollcall report--format=text|json|prometheus.--internaladds the per-app list; keep that inside the project.rollcall drift--feed=<file>, a saved endoflife.date page. ROLLCALL never fetches it itself.- In Laravel
- The package registers itself. Publish the config with
php artisan vendor:publish --tag=rollcall-config, then schedulephp artisan rollcall:scan.
Classroom rules
For noticing who is missing, not for shaming them
It enforces nothing.
No issues opened, no comments, no deploys. It exits
0whatever it finds.Totals, not names.
The public report is counts and progress only: no app names, no owners, no ranking.
Read-only everywhere.
GETandHEADonly, and a test fails if a write verb shows up insrc/. The one thing it writes is its own snapshot folder.Metrics stay off until you turn them on.
Then
/metricswants a bearer token or an allowed address. With neither set, nobody gets in.
Sources in, one register out
Where the answers come from
Three sources and one policy go in; one snapshot a day comes out. Each source has a fixture adapter, so nothing here needs a live host to try.
- Repositories
composer.jsonand.lock, from GitLab or a folder - App listthe PHP image each app runs
PROCTORone JSON report per app
CREEDpolicy.json, the dates
- Terminal
textandjsonreports - /metricsPrometheus text, off by default
- Grafanathe bundled burn-down dashboard
- Laravel pagea WayneUI dashboard of totals
ROLLCALL's place in WELD
The class list
WELD is Wayne State's application stack. Every part answers present here too.
WayneUIthe interface; the dashboard wears its classes
CREEDwrites the dates ROLLCALL reads
PROCTORchecks one app; ROLLCALL collects the results
PREFLIGHTCI templates, where PROCTOR reports are born
SESAMEthe auth package ROLLCALL hopes to count
HELMETbaseline runtime, with its own /metrics
Pinstripethe Pint and PHPStan rules this repo wears
Sourdoughstarters that begin in the front row
WELDthe whole stack, joined in one app
ROLLCALLyou are here: the fleet census and EOL burn-down
BOOSTERmoves the back row forward
SQUIREcarries the release to Citadel
DRAWBRIDGEthe Citadel sign-in gateway
ADVISORask what applies to your app