Inspectora · Test run · run 2026-09-25
Inspectora Test run, as shipped in the download
186 checks passed, 0 failed, when the suite that ships in the zip was run on 2026-09-25 with php tests/run.php. Run it yourself after you download.
== 1. expiryFrom: months added, month-end CLAMPED, 0 = never expires == PASS 1 Aug + 12m (got '2027-08-01', want '2027-08-01') PASS 31 Jan + 1m clamps to Feb 28 (2026 is not a leap year) (got '2026-02-28', want '2026-02-28') PASS 29 Feb + 12m clamps to Feb 28 (got '2025-02-28', want '2025-02-28') PASS 31 Mar + 1m clamps to Apr 30 (got '2026-04-30', want '2026-04-30') PASS 31 Dec + 1m crosses the year (got '2027-01-31', want '2027-01-31') PASS 1 Jan + 36m (got '2029-01-01', want '2029-01-01') PASS 0 months = never expires (got NULL, want NULL) PASS negative months = never expires (got NULL, want NULL) PASS a nonsense date yields nothing (got NULL, want NULL) PASS 30 Jan and 31 Jan + 1m land on the SAME day, not three days apart == 2. state: the expiry day itself is still valid == PASS expires in 90 days, warn 30 -> valid (got 'valid', want 'valid') PASS expires in 10 days, warn 30 -> expiring (got 'expiring', want 'expiring') PASS expires TODAY -> expiring, never expired (got 'expiring', want 'expiring') PASS expired yesterday -> expired (got 'expired', want 'expired') PASS exactly at the warning edge -> expiring (got 'expiring', want 'expiring') PASS one day past the edge -> valid (got 'valid', want 'valid') PASS no expiry date at all -> valid forever (got 'valid', want 'valid') PASS warn 0 means only today is amber (got 'expiring', want 'expiring') PASS a cell with no record at all (got 'none', want 'none') == 2b. a failed check holds nothing (F2b, 2026-09-24) == PASS the engine knows three results (got array ( 0 => 'pass', 1 => 'pass_remarks', 2 => 'fail', ), want array ( 0 => 'pass', 1 => 'pass_remarks', 2 => 'fail', )) PASS a fail is FAILED even with an expiry still ahead of it (got 'failed', want 'failed') PASS a fail with no expiry is FAILED, never "valid forever" (got 'failed', want 'failed') PASS a pass with remarks is read by its dates like any pass (got 'valid', want 'valid') PASS a record with no result at all (older rows) is a pass (got 'expired', want 'expired') PASS isFail reads the result and nothing else PASS a failed cell says so (got 'failed', want 'failed') PASS failed is the worst state, read first (got 'failed', want 'failed') PASS a per-row tally counts failed cells in its own key (got array ( 'valid' => 1, 'expiring' => 0, 'expired' => 0, 'missing' => 0, 'failed' => 1, ), want array ( 'valid' => 1, 'expiring' => 0, 'expired' => 0, 'missing' => 0, 'failed' => 1, )) == 3. daysUntil == PASS 10 days ahead (got 10, want 10) PASS today is 0 (got 0, want 0) PASS yesterday is -1 (got -1, want -1) PASS across a month boundary (got 31, want 31) PASS null in, null out (got NULL, want NULL) == 4. phrase: never invents a number it was not given == PASS missing (got 'never recorded', want 'never recorded') PASS overdue (got '84d overdue', want '84d overdue') PASS today (got 'expires today', want 'expires today') PASS soon (got 'in 12d', want 'in 12d') PASS valid with no date (got 'valid', want 'valid') == 5. applies: blank scope = everyone, otherwise the tag must match == PASS a blank scope applies to everyone PASS a scoped requirement applies to its own group PASS and not to another group PASS matching ignores case and stray spaces PASS a subject with no group is outside a scoped requirement == 6. dates == PASS isDate accepts a real date PASS isDate rejects 30 February PASS isDate rejects a non-date PASS isDate rejects a partial date PASS addDays forward (got '2026-09-01', want '2026-09-01') PASS addDays backward (got '2026-08-22', want '2026-08-22') == 7. tableFor: the only interpolated identifier is whitelisted == PASS subject maps to this product's table (got 'assets', want 'assets') PASS an unknown role is refused, not guessed PASS ROLES is exactly the six the engine knows PASS Domain names a table for every role == 8. the shared engine is byte-identical to the canonical copy == PASS the shipped engine matches the canonical one byte for byte PASS the engine file ships with the product PASS the two products ship the SAME code apart from the four that may differ == 9. Pdf: produces a valid, non-trivial document == PASS starts with the PDF header PASS ends with the EOF marker PASS is not a stub == PWA == PASS manifest.json is valid JSON PASS manifest has required fields PASS manifest URLs are RELATIVE (subpath-install safe) PASS manifest icons exist on disk (192 + 512 + apple-touch) PASS sw.js never caches non-GET PASS sw.js never caches page navigations (auth HTML can not enter cache storage) PASS sw.js cache name carries the CURRENT version (old caches purged on activate) PASS offline fallback page ships PASS the layout links the manifest + registers the SW PASS the layout carries a theme toggle and loads the kit == platform shape == PASS index.php migrates at boot — the ONLY upgrade path for an existing install PASS the twelve domain routes are registered PASS route segments come from Domain, so index.php is one file for both products PASS the platform routes are registered (api, mcp, 2fa, security, backup, import, healthz, openapi) PASS 2FA intercepts login + login is rate-limited PASS exactly ONE place inserts a record, and it is the engine PASS every door reaches the engine: grid cell, round attendance, REST and MCP PASS no controller recomputes an expiry date of its own PASS no controller hardcodes a physical table name — they all ask Domain PASS the write path returns refusals rather than throwing (every door must explain itself) PASS every MCP read tool declares readOnly (the core hides writes from a read key by THIS flag) PASS the MCP endpoint hands the key scope to the core PASS a read-scoped key is refused at the door, once — not per route PASS mintKey persists the scope and the key form offers it PASS the certificate hand-off emits certora's three header names, and nothing generates a certificate here PASS settings is two forms writing one row, each declaring its block PASS counterparty mail is opt-in and swallows its own failures PASS absolute URLs are built once (no doubled origin) PASS dark palette + zero hardcoded white backgrounds PASS the app layer only re-tints the accent; the kit owns the rest PASS API.md ships and documents the vendor-neutral connect recipe PASS /backup.sqlite is carved out of the blanket .sqlite block in BOTH the dev router and .htaccess PASS every cron script the docs and screens name ships (reminders.php) PASS the reminder runner is CLI-only, calls the one shared pass, and the web server denies cron/ PASS BACKUP: both backup downloads are administrators-only, and only administrators are shown them PASS WEBHOOKS: the events offered for subscription are exactly the events the code fires (record.created) PASS DASHBOARD: a completed getting-started step draws its tick (the kit draws the ✓ into <i>) == every function the routes name actually exists == PASS every route handler is defined (60 routes checked) == runtime == PASS migrate() creates the six engine tables under this domain's names PASS install() ALONE produces the current schema PASS migrate() creates the settings row PASS migrate() twice changes nothing PASS HAPPY: a completion is recorded and its expiry computed PASS REFUSAL: a future date is refused PASS REFUSAL: a nonsense date is refused PASS REFUSAL: a requirement scoped to another group is refused PASS REFUSAL: an archived subject is refused PASS REFUSAL: unknown ids are refused, not created PASS REFUSAL: a refused write leaves the table untouched PASS PERMISSION: viewer may read and may not write PASS PERMISSION: member may write records PASS PERMISSION: member may not delete PASS PERMISSION: admin may do everything PASS PERMISSION: an unknown role gets the most restrictive one PASS IDEMPOTENCY: marking attendance twice writes one record PASS the state a round stores is the word the screens compare against PASS IDEMPOTENCY: the same person cannot be booked twice PASS IDEMPOTENCY: a reminder ledger row is claimed before the send PASS IDEMPOTENCY: a failed send releases its slot PASS a renewal supersedes without deleting the old record PASS the current record is the latest achieved_on PASS coverage counts only cells that APPLY PASS the attention list is ordered worst-first, stably PASS FAILED: a failed record makes the cell failed; an earlier-dated pass does not clear it, a later pass does PASS FAILED: a fail stores no expiry, and the API never calls it "never expires" PASS FAILED: a fail comes off the in-date count (today and in the history) and heads the attention list PASS FAILED: a result the engine does not know is refused and writes nothing PASS FAILED: a pass with remarks is a pass, with its expiry PASS FAILED: the agent tool takes a result only where the product records one; otherwise it is unchanged PASS EXPORT: the matrix CSV header and cells are as documented PASS EXPORT: the records CSV header is as documented PASS HAND-OFF: exactly name,course,email — certora's importer contract PASS HAND-OFF: an expired holder is not offered a certificate PASS a fresh install writes to nobody PASS switched on with no SMTP still sends nothing PASS RUNNER: cron/reminders.php --dry-run names who is due and sends and writes nothing PASS RUNNER: cron/reminders.php writes to each person due AND sends the owner digest, over real SMTP PASS RUNNER: a real pass records when it ran and what it did PASS RUNNER: a second pass the same day attempts no mail at all PASS RUNNER: a digest the mail server refuses is retried next run PASS RUNNER: switched-on reminders that never ran are flagged, a fresh run is not PASS EVIDENCE: the bytes decide the type, not the name PASS every write path leaves an audit row PASS no audit row carries a secret PASS the calendar feed carries no email addresses PASS GDPR: names go, the compliance history stays PASS GDPR: refused while a seat is still booked PASS backup + restore parses its own file PASS BACKUP: the permission the downloads ask for is held by an administrator and by no viewer or member PASS licence: every font ships the SIL OFL text for its own family beside it PASS licence: LICENSE.txt is present and carries the IF WE STOP (CONTINUITY) clause == every backup and restore route is administrator-only == PASS BACKUP: a signed-in viewer gets 403 on GET /backup.json PASS BACKUP: a signed-in viewer gets 403 on GET /backup.sqlite PASS BACKUP: a signed-in viewer gets 403 on GET /restore PASS BACKUP: a signed-in viewer gets 403 on POST /restore PASS BACKUP: a signed-in viewer gets 403 on POST /restore/commit PASS BACKUP: a signed-in viewer gets 403 on POST /api-settings/backup-token PASS BACKUP: a signed-in viewer without the token gets nothing from GET /backup/scheduled PASS BACKUP: a signed-in member gets 403 on GET /backup.json PASS BACKUP: a signed-in member gets 403 on GET /backup.sqlite PASS BACKUP: a signed-in member gets 403 on GET /restore PASS BACKUP: a signed-in member gets 403 on POST /restore PASS BACKUP: a signed-in member gets 403 on POST /restore/commit PASS BACKUP: a signed-in member gets 403 on POST /api-settings/backup-token PASS BACKUP: a signed-in member without the token gets nothing from GET /backup/scheduled PASS BACKUP: an admin still downloads the JSON backup == the dashboard speaks this product's language == PASS WORDS: the main button reads "Open the due board", never "Open the what is …" PASS WORDS: the first-run step asks for the inspections already done, not what people hold PASS WORDS: the certificates box describes evidence on the record, with no Certora hand-off PASS WORDS: the assets register is not drawn with a person icon PASS UI: the sidebar brand cannot be shrunk to nothing (flex-shrink:0) == fix wave: the webhook buttons work == PASS webhooks: Add creates the endpoint (no 404) PASS webhooks: Pause switches it off (no 404) PASS webhooks: pressing it again switches it back on PASS webhooks: the page offers Pause and Delete for the endpoint, at those same addresses PASS webhooks: Delete removes it (no 404) == F2b: a failed check, through the screens == PASS FAILED: the grid form records a fail, and the cell comes back failed (got 'failed', want 'failed') PASS FAILED: the stored record says fail and carries no next-due date PASS FAILED: the grid paints the cell failed, offers a "Failed" filter and legend, and the form asks for the result PASS FAILED: a round sheet asks for each row's result, and a fail on it is recorded as a fail PASS FAILED: the dashboard counts failed checks with the expired ones PASS FAILED: the REST door hands the posted result to the same reader as the other doors -- F2b: the demo ribbon's wording and size; the JSON backup's secrets -- PASS demo ribbon: Inspectora has no public page, so a page served without sign-in keeps the admin wording too PASS demo ribbon: an administrator's page keeps the admin wording PASS demo ribbon: below 600px it starts as the small "Own it" pill; a tap opens it and the choice holds for the session PASS demo ribbon: registered only behind the .demo-mode marker, and non-HTML output passes through untouched PASS backup: the JSON download carries none of the 10 credentials this schema holds (settings.oidc_client_secret, settings.backup_token_hash, settings.smtp_pass, users.password_hash, users.totp_secret, users.totp_recovery, api_keys.token_hash, webhooks.secret, invites.token_hash, feed_tokens.token_hash) ———————————————————————————————————————————————————— ALL 186 TESTS PASSED (0 failed)