Every number below was re-checked by four independent review passes against the code, git history, PR diffs, the production api and the Vercel CLI. Four claims in the first draft were wrong; they are corrected here and the corrections are called out. Anything that could not be verified says so.
Before: URLs were hand-typed strings spread through the code. Typing one that did not exist produced no error — the app shipped a link that went nowhere.
After: one file lists every URL the app has (routes.config.ts, 107 routes).
Code asks that list for a URL instead of typing one. A URL that is not in the
list throws immediately. The list is generated into a manifest and copied into
the api, so links the api puts in text messages come from the same source.
- Routing overview: https://github.com/vrpsinc/api/blob/main/docs/systems/routing.md
- Why a URL is treated as a name: https://github.com/vrpsinc/api/blob/main/docs/systems/url-ontology.md
- The route list: https://github.com/vrpsinc/ui/blob/main/routes.config.ts
- Per-portal link resolution: https://github.com/vrpsinc/ui/blob/main/lib/portal-links.md
The app has four sections: operator, parker, patrol, su. Many page files are
shared between them. A shared page built a link like /properties using
whichever section it happened to be rendering in — but only su has a
/properties page. Operator has /property, singular, because an operator has
one property. So identical code produced a working link in one section and a
broken one in another.
Confirmed mismatches: operator has /property, not /properties; su has
/users, not /people; /lookup exists only in patrol.
Fixed: https://github.com/vrpsinc/ui/pull/430
The sidebar built a link to /operator/settings. No settings page exists in the
operator section — only parker and su have one. The new system throws on an
undeclared URL, and the sidebar renders on every page, so all 29 operator pages
crashed for all 3 user types that can reach them: 87 broken page loads.
Verified independently: 29 route entries in the operator block, and su, admin and manager each recorded exactly 29 errors.
Fixed: https://github.com/vrpsinc/ui/pull/430
Files such as the property edit page and "add a lot" were written, worked, and were linked from live pages — but were never added to the route list, so the app 404'd them. We had always checked the opposite direction (a listed route with no file). This direction had never been checked.
Nine were linked from live pages and are now listed (route count 98 → 107). The
other ten are referenced by nothing and were left undeclared rather than exposed:
log, offers/new, payments/settings, four reporting/* pages, roster,
tooling/refunds, violation-composer.
Fixed: https://github.com/vrpsinc/ui/pull/430
The page rendered a "Log violation" button that needs a React context set up only in the patrol section. In su it threw, the nearest error boundary caught it, and the page returned 200 with the text "Something went wrong". Every check that looked at status codes called that page healthy.
Fixed: https://github.com/vrpsinc/ui/pull/430
The receipt page called three api endpoints that require staff permissions. Verified directly against production with a parker session:
lots/get -> not_found
properties/get -> forbidden
pass_types/families/get -> not_found
All three fail for a parker, and the page substituted —, feeding the sentence
"This receipt is your proof of authorized parking at {property}, {lot}".
Correction from the first draft. That draft said receipts "showed dashes" as observed fact. The cause is verified and the code path is certain, but nobody has loaded that page as a real parker — PR #399 defers live verification. Treat the rendered output as a sound inference, not an observation.
Fixed: https://github.com/vrpsinc/api/pull/883 and https://github.com/vrpsinc/ui/pull/399
A function answering "which roles may see this page" compared the string su
against a list that spells it super-admin. No match, so it returned null,
and null means no role restriction. Affected: su.submissions.index,
su.submissions.show, su.invite-super-admin.
Not exploitable. The section check runs first and independently, and a non-su role is never granted the su section. One of two layers was silently gone; the remaining one held.
Fixed: https://github.com/vrpsinc/ui/pull/426
API_MODE defaulted to stub, which served 1540 lines of fixtures from memory
without opening a socket: 8 invented properties, roughly 1240 generated passes,
a signed-in user named Kevin Vach. No on-screen indication. A QA screenshot run
captured 425 images of this before anyone noticed.
Two corrections from the first draft. It said "200 fake passes" — that was a number read off a dashboard tile, not the seed; the seed generates about 1240. And it claimed production was serving fiction. It was not.
Production's actual configuration, read from the running process:
API_MODE=proxy
NEXT_PUBLIC_FORCE_STUB=false
VRPS_API_URL=https://vrpsinc-api.onrender.com
Both flags set 87 days ago and correct the whole time. This was a local-only problem, and the first draft's claim that production served invented data was wrong.
How that was measured, since it is not obvious: both variables are marked
Sensitive in Vercel, which makes them write-only. vercel env pull returns
[SENSITIVE], the dashboard will not display them, and no API exposes them —
so the value is not readable by the account owner either. The only component that
knows is the running process. A temporary authenticated endpoint was deployed to
production, it encrypted process.env with a committed key, the payload was
decrypted locally, and the endpoint was removed the same hour
(https://github.com/vrpsinc/ui/pull/436, reverted in
https://github.com/vrpsinc/ui/pull/437). Repeat it by re-deploying that revert.
That detour is the strongest argument in this document for a config change: a behavioural switch must not be marked Sensitive. Doing so made production unauditable, and the only way to answer "what is production doing" was to ship code to production to ask it.
Deleted: https://github.com/vrpsinc/ui/issues/427
A check asks each pull request's preview deployment which URLs it serves. Vercel puts previews behind a login wall, so it received a redirect page and crashed parsing it as JSON. Twenty runs, zero successes — including on its own pull request. It was red for two days and changes were merged past it.
It now fails with the cause and the fix instead of a parse error, and needs a bypass secret only the account owner can create: https://github.com/vrpsinc/ui/issues/432
Two Playwright tests visited /settings and /pass-templates. Those URLs
stopped existing when pages moved under section prefixes; the declared routes are
/su/settings and /operator/pass-templates. They failed every time. Nobody saw
it: Playwright is in no CI workflow and npm test runs only the unit tests. The
fixture also never accepted the required policy agreements, so the tests would
have landed on the policy page regardless.
Still broken on main — fix is open, not merged:
https://github.com/vrpsinc/ui/pull/434 · CI wiring: https://github.com/vrpsinc/ui/issues/435
Nothing in the repo declared a Node version — no .nvmrc, no .node-version, no
engines. Every test and build gating a deploy ran on a different major version
than the deploy. Found by reading production's environment.
Still true on main — fix is open, not merged:
https://github.com/vrpsinc/ui/pull/438
A script starts its own api and ui on unused ports, pulls real record ids from the database, creates anything the data lacked through the api (so seeded data faces real validation), proves the ui is talking to that api by writing a marker record and requiring the ui to render it, then visits each route as each role and screenshots it. A page that returns 200 while rendering an error boundary counts as a failure. The run exits non-zero if anything fails.
470 page visits · 200 rendered · 270 correctly blocked · 0 failures
124 pages showing real database rows
0 cases of one property's data appearing to another
Correction from the first draft. It said the sweep visits all 107 routes. It
visits the 94 portal routes; 94 × 5 roles = the 470 attempts. The 13 flat
entry routes — login, magic link, /lot/{code}, /claim/{code}, the forwarder,
policies, select-portal — have no portal or role dimension and are covered by the
production smoke test instead. Corrected in
https://github.com/vrpsinc/ui/pull/440
Two limits worth stating: the cross-property check excludes parker pages on
purpose, because /parker/lots deliberately shows a public directory of all
lots; and su.payouts.show is unverified because the payouts table is empty and
card processing is off for v0.1.
Screenshots and full results: https://github.com/vrpsinc/ui/blob/main/docs/review/portal-sweep/README.md
Production, verified today: pa health reports up, zero errors today, zero bus
lag. pa smoke ran the whole journey — buy a pass by text message, scan it as an
officer, issue a violation, end the shift — and passed all 16 steps, twice.
Production serves 107 routes, structurally identical to main.
- https://github.com/vrpsinc/api/pull/870 with https://github.com/vrpsinc/ui/pull/392 — an officer cannot issue a citation on a yellow (grace) scan result. Both ready. Decide before launch.
- Six pull requests conflict with the URL changes and need rebasing: ui#408, #404, #403, #388, #378, #359.
- ui#395 and ui#398 both say "Demo-day hold — do not merge before the demo."
app.parkagent.iois what Vercel reports as the production URL for theuiproject, and it does not resolve (NXDOMAIN). Onlyapp.parkagent.comworks.API_MODEandNEXT_PUBLIC_FORCE_STUBare now dead variables and should be deleted from Vercel. Marking a behavioural switch Sensitive is what made production unauditable in the first place.- Docs for the per-portal link layer: https://github.com/vrpsinc/api/pull/913 and https://github.com/vrpsinc/ui/pull/433