Engineering Standards¶
The bar this codebase is held to, and the review that enforces it.
1. Adversarial review¶
Run before pushing, not after somebody asks:
bin/dev/review # the working branch against master
bin/dev/review <commit-sha> # one commit
bin/dev/review --uncommitted # the working tree, before committing
The reviewing CLI is named by $REVIEW_AGENT, read from the untracked .env and falling
back to your shell profile. It is deliberately not written down here: the tool can be
swapped without editing anything, and no vendor name enters the repository. REVIEW_AGENT
is a command line rather than a bare binary, so a reviewer that wants a subcommand and one
that wants a flag are both a setting; set REVIEW_AGENT_STDIN=1 for a reviewer that reads
its prompt from standard input. See .env.example.
The review is a local step, not a CI job — the reviewing CLI is authenticated on your machine. CI enforces the deterministic checks; this one is a discipline.
The review brief — single source of truth, extracted verbatim by the script, so it cannot drift:
"You are reviewing a change to InteLIS, a PHP laboratory information system covering viral load, EID, COVID-19, TB, CD4, hepatitis and custom tests, deployed as a fleet of laboratory instances that sync to a central instance. Do not summarize the code. Find: (1) any query reaching test, patient or user data without the lab scope (
CommonService::labScopeWhere,labAdminScopeWhere,$_SESSION['labId']), and any administrative write a restricted operator could reach on a cloud instance (CommonService::isCloudLisNonAdmin); (2) SQL built by concatenating request data instead of binding it; (3) anything that can silently lose entered data — a request save that writes result columns, two fields sharing onenamein a form (PHP keeps the last, so the earlier value is discarded), an index misalignment across parallel POST arrays, a status update that nulls a column it did not intend to touch, or a write togeneric_test_results, which has no audit triggers and is therefore unrecoverable; (4) schema changes made anywhere butsys/migrations/, migrations that are not re-runnable on both fresh and upgraded installs, and a new migration without the matching version bump; (5) any place patient data or a lab identifier is taken from the request rather than from the credential or an explicit allowlist before reaching an API response or a remote payload; (6) user-visible strings that bypass the translation helpers, and output escaped with the wrong helper for its context — HTML body, HTML attribute, JS string, or grid tooltip; (7) a defect fixed in one country form while its siblings carry the same copy-pasted code; (8) tests that assert the happy path but would still pass if the invariant were deleted. Rank findings by severity. If you find nothing in a category, say 'clear' — don't pad."
Where the second opinion matters most: anything touching lab scoping or the cloud-instance admin gate, the result-entry and import paths, every migration, and the remote sync and API surfaces. Routine CRUD does not need double review — don't ritualize it into overhead.
Discipline rule: the same bar applies to every change regardless of how it was written. Nothing lands on "it runs". The tests and the review pass are the bar.
2. What counts as a finding¶
A defect with a failure scenario: concrete inputs or state, and the wrong output or lost data that results. "This could be cleaner" is not a finding. A trade-off already recorded in the docs is a rebuttal, not a fix.
Address or explicitly rebut every finding before merging. A rebuttal is a sentence saying why the code is right, not silence.
3. Standing invariants¶
These are the rules the brief is derived from. They are here so a change can be checked against them without running a review.
- Lab scope. Every read of test, patient or user data on a multi-lab instance goes
through
labScopeWhere/labAdminScopeWhere. A missing scope is a data-leak bug, not a style issue. - Schema changes live in
sys/migrations/.sql/init.sqlis a seed for fresh installs and is not edited to change the schema; migrations replay on fresh installs too, so they must be re-runnable and must not assume an upgraded database. A new migration means a version bump incomposer.jsonandversion.php, andcomposer update --lockso the lockfile hash stays current. - Request saves do not touch result columns. Add/edit request helpers write request
fields only.
generic_test_resultshas no audit triggers, so a bad write there is unrecoverable. - One name per field. Two controls sharing a
namein one form means PHP keeps the last and silently discards the first. Same for a duplicateid, which quietly breaks the#idhandler andlabel[for]. - Country forms are copies. A defect found in one country's form is usually present in its siblings. Fix the family, not the instance.
- A password handed to MySQL is the weakest of the three ways to give it one. MySQL reads
option files before anything passed to it, and the precedence is command line > option file
environment.
setup_mysql_config()writes/root/.my.cnfwith a password on every machine, and these scripts run as root, so a password supplied inMYSQL_PWDis silently ignored in favour of that file's. Any script shelling out tomysql,mysqldump,mysqladminormysqlcheckwith a password of its own passes--no-defaultsas the first argument, or MySQL ignores the flag without saying so — and only when a password was actually supplied, since an empty one means the option file is the intended source. It always presents the same way: a credential that is definitely correct being refused, with nothing on screen to explain it. Three occurrences so far — db-tools 3.3.0,db-backup.sh, andrepair_html_escaped_db_password(), where it left the repair unable to fire on the machines it was written for. Known and deliberately not fixed: theSET PERSIST sql_modecalls insetup.shandupgrade.sh, where the same setting is also written tomysqld.cnfand the failure is printed rather than swallowed. mysqladmin pingis not a credential check. It answers "is the server alive", and answers yes when access is denied — which is why it is the right probe for "is MySQL up" and useless for "is this password correct". Test a password by running a statement.- User-visible strings are translatable and escaped with the helper matching their output context.
- Exports use OpenSpout.
- A caught error still has to reach someone.
LegacyRequestHandlerdiscards the page buffer on a throw and hands off toErrorResponseGenerator, which renders the styled page for a browser, JSON for an AJAX caller, and logs both with an error ID the user can quote. A terminaltry/catchthat only logs bypasses all of that and returns a blank 200: the page looks like it worked. Log and rethrow, or handle it visibly (flash message plus redirect). Swallowing is only correct where the failure is genuinely optional, and that belongs in service code, not at the bottom of a page. Log$e->getFile()/$e->getLine(), never__FILE__/__LINE__— the latter records the catch, not the failure.
4. Before you push¶
composer testgreen.php -lclean on every changed PHP file.bin/dev/reviewrun, and every finding addressed or rebutted.- Shell scripts under
scripts/run, not just read.bash -nproves syntax and nothing else. Use a privileged systemd container and stage the machine the way the fleet actually is — MySQL installed,/root/.my.cnfpresent, an installation under/var/www/intelis— then break the thing the script is supposed to handle and watch it handle it. Every MySQL credential bug above survived review and reading, and was found in the first minute of running the script on a machine configured like a lab's.